Version: 2.3.1-GIT (using KDE 4.4.4) OS: Linux Reproducible: Didn't try
I noticed that the fuzzy exact slider doesn't always behave predictable. Let me explain, when I chose playlist length equal to 1:30 and set the value of fuzzy-exact to anything but completely fuzzy, the length of the playlist is always very close to 1:30, which is pretty good. As soon as I set the value to complete fuzzy, the playlist length is not even close to 1:30. The expected behavior of this should be that for example complete fuzzy is about 20% off of the length, and complete exact about 1 or 2% off. It think this would be better, but this is not a crucial bug of course. --- Sorry that for the empty first message, but the bug site did weird (or I clicked on submit without knowing it).
I've noticed this too, and I'm working on it.
A src/playlistgenerator/constraints/TagMatchSupport.cpp [License: UNKNOWN] commit 3702226af3aae8a8cda698b5f2fca2286119e0ca Author: Soren Harward <stharward@gmail.com> Date: Fri Jul 16 09:24:07 2010 -0400 APG: Rewrote fuzzy numerical comparison code After much testing, I've determined that the fuzzy numerical comparison function just gives results that are more like what you'd expect when it uses a hard-coded weight for each field, rather than trying to calculate a weight on the fly. BUG: 242281 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f22a61c..ea5d155 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -532,7 +532,7 @@ set(apg_SRCS playlistgenerator/constraints/PlaylistLength.cpp playlistgenerator/constraints/PreventDuplicates.cpp playlistgenerator/constraints/TagMatch.cpp - playlistgenerator/constraints/TagMatchFieldsModel.cpp + playlistgenerator/constraints/TagMatchSupport.cpp playlistgenerator/constraints/TrackSpreader.cpp ) diff --git a/src/playlistgenerator/Constraint.cpp b/src/playlistgenerator/Constraint.cpp index ce6783d..40c4737 100644 --- a/src/playlistgenerator/Constraint.cpp +++ b/src/playlistgenerator/Constraint.cpp @@ -20,33 +20,4 @@ #include "core/support/Debug.h" -#include <QString> - -const double Constraint::magicStrictnessWeight = 3.0; - -Constraint::Constraint( ConstraintNode* p ) : ConstraintNode( p ) {} - -double Constraint::compare( const QString& a, const int comparison, const QString& b, double strictness ) const -{ - Q_UNUSED( strictness ); // strictness is (currently) meaningless for string comparisons - if ( comparison == CompareStrEquals ) { - if ( a.compare( b, Qt::CaseInsensitive ) == 0 ) - return 1.0; - } else if ( comparison == CompareStrStartsWith ) { - if ( a.startsWith( b, Qt::CaseInsensitive ) ) - return 1.0; - } else if ( comparison == CompareStrEndsWith ) { - if ( a.endsWith( b, Qt::CaseInsensitive ) ) - return 1.0; - } else if ( comparison == CompareStrContains ) { - if ( a.contains( b, Qt::CaseInsensitive ) ) - return 1.0; - } else if ( comparison == CompareStrRegExp ) { - QRegExp rx( b ); - if ( rx.indexIn( a ) >= 0 ) - return 1.0; - } else { - return 0.0; - } - return 0.0; -} +Constraint::Constraint( ConstraintNode* p ) : ConstraintNode( p ) {} \ No newline at end of file diff --git a/src/playlistgenerator/Constraint.h b/src/playlistgenerator/Constraint.h index d467a8e..ba28622 100644 --- a/src/playlistgenerator/Constraint.h +++ b/src/playlistgenerator/Constraint.h @@ -19,61 +19,16 @@ #include "ConstraintNode.h" -#include "core/meta/Meta.h" - -#include <QDomElement> -#include <QHash> -#include <QList> #include <QObject> -#include <QString> -#include <QtGlobal> -#include <math.h> /* ABC for all Constraints */ class Constraint : public ConstraintNode { Q_OBJECT public: - enum NumComparison { CompareNumLessThan, CompareNumEquals, CompareNumGreaterThan }; - enum StrComparison { CompareStrEquals, CompareStrStartsWith, CompareStrEndsWith, CompareStrContains, CompareStrRegExp }; - enum DateComparison { CompareDateBefore, CompareDateOn, CompareDateAfter, CompareDateWithin }; - - static const double magicStrictnessWeight; - virtual int getNodeType() const { return ConstraintNode::ConstraintType; } protected: Constraint( ConstraintNode* ); - - // A couple of helper functions for subclasses - double compare(const QString&, const int, const QString&, const double strictness=1.0) const; - template <typename T> double compare(const T, const int, const T, const double strictness=1.0) const; }; -// Templated function from Constraint -template <typename T> double Constraint::compare( const T a, const int comparison, const T b, const double strictness ) const { - - /* There's no mathematical rigor to this factor; I came up with some test - * cases and what answers I expected, and tried a bunch of different - * factors. This one behaved closest to what I wanted. -- sth */ - - double factor = ( exp( magicStrictnessWeight * strictness) / ( sqrt( (double) b ) + 1.0 ) ); - if ( comparison == CompareNumEquals ) { - // fuzzy equals -- within 1% - if ( qAbs( a - b ) < ( ( a + b ) / 200.0 ) ) { - return 1.0; - } else if ( a > b ) { - return exp( factor * ( b - a ) ); - } else { - return exp( factor * ( a - b ) ); - } - } else if ( comparison == CompareNumGreaterThan ) { - return (a > b) ? 1.0 : exp( factor * ( a - ( b * 1.05 ) ) ); - } else if ( comparison == CompareNumLessThan ) { - return (a < b) ? 1.0 : exp( factor * ( b - ( a * 1.05 ) ) ); - } else { - return 0.0; - } - return 0.0; -} - #endif diff --git a/src/playlistgenerator/TODO b/src/playlistgenerator/TODO index 8e30fa5..1727bb1 100644 --- a/src/playlistgenerator/TODO +++ b/src/playlistgenerator/TODO @@ -8,8 +8,7 @@ Backend: Constraints: - fix preventduplicates: the delta functions are kinda broken - last.fm or echonest similar artists constraint -- proper handling of empty fingerprints in SimilarityChain +- playlist length (ie, # of tracks) constraint GUI: -- "simple" presets? -- context menu for APGCategory +- context menu for APGCategory \ No newline at end of file diff --git a/src/playlistgenerator/constraints/Checkpoint.cpp b/src/playlistgenerator/constraints/Checkpoint.cpp index eb30407..57237e9 100644 --- a/src/playlistgenerator/constraints/Checkpoint.cpp +++ b/src/playlistgenerator/constraints/Checkpoint.cpp @@ -32,6 +32,7 @@ #include <QtGlobal> #include <climits> +#include <math.h> Constraint* ConstraintTypes::Checkpoint::createFromXml( QDomElement& xmlelem, ConstraintNode* p ) diff --git a/src/playlistgenerator/constraints/PlaylistLength.cpp b/src/playlistgenerator/constraints/PlaylistLength.cpp index c5df1e3..76b4967 100644 --- a/src/playlistgenerator/constraints/PlaylistLength.cpp +++ b/src/playlistgenerator/constraints/PlaylistLength.cpp @@ -29,6 +29,7 @@ #include <QtGlobal> #include <stdlib.h> +#include <math.h> Constraint* ConstraintTypes::PlaylistLength::createFromXml( QDomElement& xmlelem, ConstraintNode* p ) @@ -80,7 +81,7 @@ ConstraintTypes::PlaylistLength::PlaylistLength( QDomElement& xmlelem, Constrain ConstraintTypes::PlaylistLength::PlaylistLength( ConstraintNode* p ) : Constraint( p ) , m_length( 0 ) - , m_comparison( Constraint::CompareNumEquals ) + , m_comparison( CompareNumEquals ) , m_strictness( 1.0 ) { } @@ -187,9 +188,9 @@ ConstraintTypes::PlaylistLength::swapTracks( const Meta::TrackList&, const int, int ConstraintTypes::PlaylistLength::suggestInitialPlaylistSize() const { - if ( m_comparison == Constraint::CompareNumLessThan ) { + if ( m_comparison == CompareNumLessThan ) { return m_length / 300000; - } else if ( m_comparison == Constraint::CompareNumGreaterThan ) { + } else if ( m_comparison == CompareNumGreaterThan ) { return m_length / 180000; } else { return m_length / 240000; @@ -201,7 +202,7 @@ ConstraintTypes::PlaylistLength::vote( const Meta::TrackList& playlist, const Me { ConstraintNode::Vote* v = 0; - if ( m_comparison == Constraint::CompareNumLessThan ) { + if ( m_comparison == CompareNumLessThan ) { if ( m_totalLength > m_length) { int longestLength = 0; int longestPosition = -1; @@ -217,14 +218,14 @@ ConstraintTypes::PlaylistLength::vote( const Meta::TrackList& playlist, const Me v->operation = ConstraintNode::OperationDelete; v->place = longestPosition; } - } else if ( m_comparison == Constraint::CompareNumGreaterThan ) { + } else if ( m_comparison == CompareNumGreaterThan ) { if ( m_totalLength < m_length) { v = new ConstraintNode::Vote; v->operation = ConstraintNode::OperationInsert; v->place = KRandom::random() % ( playlist.size() + 1 ); v->track = domain.at( KRandom::random() % domain.size() ); } - } else if ( m_comparison == Constraint::CompareNumEquals ) { + } else if ( m_comparison == CompareNumEquals ) { int deviation = qAbs( m_totalLength - m_length ); if ( m_totalLength > m_length ) { int randomIdx = KRandom::random() % playlist.size(); @@ -250,11 +251,11 @@ ConstraintTypes::PlaylistLength::vote( const Meta::TrackList& playlist, const Me QString ConstraintTypes::PlaylistLength::comparisonToString() const { - if ( m_comparison == Constraint::CompareNumEquals ) { + if ( m_comparison == CompareNumEquals ) { return QString( i18nc("duration of playlist equals some time", "equals") ); - } else if ( m_comparison == Constraint::CompareNumGreaterThan ) { + } else if ( m_comparison == CompareNumGreaterThan ) { return QString( i18n("longer than") ); - } else if ( m_comparison == Constraint::CompareNumLessThan ) { + } else if ( m_comparison == CompareNumLessThan ) { return QString( i18n("shorter than") ); } else { return QString( i18n("unknown comparison") ); @@ -265,11 +266,11 @@ double ConstraintTypes::PlaylistLength::transformLength( const qint64 l ) const { double factor = m_strictness * 0.0003; - if ( m_comparison == Constraint::CompareNumEquals ) { + if ( m_comparison == CompareNumEquals ) { return 4.0 / ( ( 1.0 + exp( factor*( double )( l - m_length ) ) )*( 1.0 + exp( factor*( double )( m_length - l ) ) ) ); - } else if ( m_comparison == Constraint::CompareNumLessThan ) { + } else if ( m_comparison == CompareNumLessThan ) { return 1.0 / ( 1.0 + exp( factor*( double )( l - m_length ) ) ); - } else if ( m_comparison == Constraint::CompareNumGreaterThan ) { + } else if ( m_comparison == CompareNumGreaterThan ) { return 1.0 / ( 1.0 + exp( factor*( double )( m_length - l ) ) ); } return 1.0; diff --git a/src/playlistgenerator/constraints/PlaylistLength.h b/src/playlistgenerator/constraints/PlaylistLength.h index 4a87a5a..d21ac71 100644 --- a/src/playlistgenerator/constraints/PlaylistLength.h +++ b/src/playlistgenerator/constraints/PlaylistLength.h @@ -38,6 +38,8 @@ namespace ConstraintTypes { class PlaylistLength : public Constraint { Q_OBJECT + enum NumComparison { CompareNumLessThan, CompareNumEquals, CompareNumGreaterThan }; + public: static Constraint* createFromXml(QDomElement&, ConstraintNode*); static Constraint* createNew(ConstraintNode*); @@ -47,7 +49,7 @@ namespace ConstraintTypes { virtual void toXml(QDomDocument&, QDomElement&) const; virtual QString getName() const; - + virtual Collections::QueryMaker* initQueryMaker(Collections::QueryMaker*) const; virtual double satisfaction(const Meta::TrackList&); virtual double deltaS_insert(const Meta::TrackList&, const Meta::TrackPtr, const int) const; diff --git a/src/playlistgenerator/constraints/TagMatch.cpp b/src/playlistgenerator/constraints/TagMatch.cpp index 22a2cf5..29c9933 100644 --- a/src/playlistgenerator/constraints/TagMatch.cpp +++ b/src/playlistgenerator/constraints/TagMatch.cpp @@ -60,6 +60,7 @@ ConstraintTypes::TagMatch::registerMe() ConstraintTypes::TagMatch::TagMatch( QDomElement& xmlelem, ConstraintNode* p ) : MatchingConstraint( p ) + , m_comparer( new Comparer() ) , m_fieldsModel( new TagMatchFieldsModel() ) { QDomAttr a; @@ -82,7 +83,7 @@ ConstraintTypes::TagMatch::TagMatch( QDomElement& xmlelem, ConstraintNode* p ) if ( m_fieldsModel->type_of( m_field ) == FieldTypeInt ) { m_value = a.value().toInt(); } else if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) { - if ( m_comparison == Constraint::CompareDateWithin ) { + if ( m_comparison == CompareDateWithin ) { QStringList parts = a.value().split(" "); if ( parts.size() == 2 ) { int u = parts.at( 0 ).toInt(); @@ -114,17 +115,19 @@ ConstraintTypes::TagMatch::TagMatch( QDomElement& xmlelem, ConstraintNode* p ) ConstraintTypes::TagMatch::TagMatch( ConstraintNode* p ) : MatchingConstraint( p ) - , m_comparison( Constraint::CompareStrEquals ) + , m_comparison( CompareStrEquals ) , m_field( "title" ) , m_invert( false ) , m_strictness( 1.0 ) , m_value() + , m_comparer( new Comparer() ) , m_fieldsModel( new TagMatchFieldsModel() ) { } ConstraintTypes::TagMatch::~TagMatch() { + delete m_comparer; delete m_fieldsModel; } @@ -192,47 +195,43 @@ ConstraintTypes::TagMatch::initQueryMaker( Collections::QueryMaker* qm ) const { if ( ( m_fieldsModel->type_of( m_field ) == FieldTypeInt ) ) { int v = m_value.toInt(); - - double factor; - int range; - if ( m_field != "length" ) { - // compute fuzzy ranges -- this marks the boundary beyond which the fuzzy match probability is less than 1% - factor = exp( Constraint::magicStrictnessWeight * m_strictness ) / ( sqrt(( double )v ) + 1.0 ); // duplicated from Constraint::compare() - range = (int)ceil( 4.6051702 / factor ); - } else { - // small kludge to get fuzziness to play better in the case of track lengths - factor = exp( Constraint::magicStrictnessWeight * m_strictness ) / ( sqrt(( double )v/1000.0 ) + 1.0 ); - range = (int)ceil( 4605.1702 / factor ); - } - if ( m_comparison == Constraint::CompareNumEquals ) { + int range = static_cast<int>( m_comparer->rangeNum( m_strictness, m_fieldsModel->meta_value_of( m_field ) ) ); + if ( m_comparison == CompareNumEquals ) { if ( !m_invert ) { - qm->beginAnd(); - qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::GreaterThan ); - qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::LessThan ); - qm->endAndOr(); + if ( m_strictness < 0.99 ) { // fuzzy approximation of "1.0" + qm->beginAnd(); + qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::GreaterThan ); + qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::LessThan ); + qm->endAndOr(); + } else { + qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v, Collections::QueryMaker::Equals ); + } + } else { + if ( m_strictness > 0.99 ) { + qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), v, Collections::QueryMaker::Equals ); + } } - } else if ( m_comparison == Constraint::CompareNumGreaterThan ) { + } else if ( m_comparison == CompareNumGreaterThan ) { if ( m_invert ) qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::GreaterThan ); else qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::GreaterThan ); - } else if ( m_comparison == Constraint::CompareNumLessThan ) { + } else if ( m_comparison == CompareNumLessThan ) { if ( m_invert ) qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), v - range, Collections::QueryMaker::LessThan ); else qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), v + range, Collections::QueryMaker::LessThan ); } } else if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) { - double factor = ( exp( 5.0 * m_strictness ) ) / 1e6; // duplicated from this::dateComparison() - uint range = (uint)ceil( 4.6051702 / factor ); uint referenceDate = 0; - if ( m_comparison == Constraint::CompareDateBefore ) { + int range = m_comparer->rangeDate( m_strictness ); + if ( m_comparison == CompareDateBefore ) { referenceDate = m_value.toDateTime().toTime_t(); if ( m_invert ) qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::LessThan ); else qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::LessThan ); - } else if ( m_comparison == Constraint::CompareDateOn ) { + } else if ( m_comparison == CompareDateOn ) { referenceDate = m_value.toDateTime().toTime_t(); if ( !m_invert ) { qm->beginAnd(); @@ -240,13 +239,13 @@ ConstraintTypes::TagMatch::initQueryMaker( Collections::QueryMaker* qm ) const qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::LessThan ); qm->endAndOr(); } - } else if ( m_comparison == Constraint::CompareDateAfter ) { + } else if ( m_comparison == CompareDateAfter ) { referenceDate = m_value.toDateTime().toTime_t(); if ( m_invert ) qm->excludeNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate + range, Collections::QueryMaker::GreaterThan ); else qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::GreaterThan ); - } else if ( m_comparison == Constraint::CompareDateWithin ) { + } else if ( m_comparison == CompareDateWithin ) { QDateTime now = QDateTime::currentDateTime(); DateRange r = m_value.value<DateRange>(); switch ( r.second ) { @@ -268,22 +267,22 @@ ConstraintTypes::TagMatch::initQueryMaker( Collections::QueryMaker* qm ) const qm->addNumberFilter( m_fieldsModel->meta_value_of( m_field ), referenceDate - range, Collections::QueryMaker::GreaterThan ); } } else if ( m_fieldsModel->type_of( m_field ) == FieldTypeString ) { - if ( m_comparison == Constraint::CompareStrEquals ) { + if ( m_comparison == CompareStrEquals ) { if ( m_invert ) qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, true ); else qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, true ); - } else if ( m_comparison == Constraint::CompareStrStartsWith ) { + } else if ( m_comparison == CompareStrStartsWith ) { if ( m_invert ) qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, false ); else qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), true, false ); - } else if ( m_comparison == Constraint::CompareStrEndsWith ) { + } else if ( m_comparison == CompareStrEndsWith ) { if ( m_invert ) qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, true ); else qm->addFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, true ); - } else if ( m_comparison == Constraint::CompareStrContains ) { + } else if ( m_comparison == CompareStrContains ) { if ( m_invert ) qm->excludeFilter( m_fieldsModel->meta_value_of( m_field ), m_value.toString(), false, false ); else @@ -405,6 +404,13 @@ ConstraintTypes::TagMatch::vote( const Meta::TrackList& playlist, const Meta::Tr return 0; } +void +ConstraintTypes::TagMatch::audit( const Meta::TrackList& tl ) const +{ + foreach( const Meta::TrackPtr t, tl ) { + debug() << t->prettyName() << matches( t ); + } +} const QBitArray ConstraintTypes::TagMatch::whatTracksMatch( const Meta::TrackList& tl ) @@ -423,114 +429,52 @@ ConstraintTypes::TagMatch::constraintMatchType() const return ( 0 << 28 ) + m_fieldsModel->index_of( m_field ); } -double -ConstraintTypes::TagMatch::dateComparison( uint trackDate ) const -{ - /* comparing dates is a little bit tricky, so I split it off into its own function */ - int comp; - uint referenceDate = 0; - if ( m_comparison == Constraint::CompareDateWithin ) { - comp = Constraint::CompareDateAfter; - QDateTime now = QDateTime::currentDateTime(); - DateRange r = m_value.value<DateRange>(); - switch ( r.second ) { - case 0: - referenceDate = now.addDays( -1 * r.first ).toTime_t(); - break; - case 1: - referenceDate = now.addMonths( -1 * r.first ).toTime_t(); - break; - case 2: - referenceDate = now.addYears( -1 * r.first ).toTime_t(); - break; - default: - break; - } - } else { - comp = m_comparison; - referenceDate = m_value.toDateTime().toTime_t(); - } - - /* I decided to keep the comparison logic here instead of passing it down - * to Constraint::compare() because ::compare() calculates the strictness - * factor in proportion to the values that were passed to it. The numbers - * involved in date calculations (ie, seconds since the epoch) are so large - * that they make ::compare()'s factor pretty much useless, plus the - * strictness factor gets smaller when later dates are chosen. This is a - * more useful strictness factor for date calculations. -- stharward */ - - double r = 0.0; - double factor = ( exp( 5.0 * m_strictness ) ) / 1e6; - if ( comp == Constraint::CompareDateOn ) { - // fuzzy equals -- within 18 hours - if ( qAbs( (double)trackDate - (double)referenceDate ) < ( 64800.0 ) ) - r = 1.0; - else if ( trackDate > referenceDate ) - r = exp( factor * ( (double)referenceDate - (double)trackDate ) ); - else - r = exp( factor * ( (double)trackDate - (double)referenceDate ) ); - } else if ( comp == Constraint::CompareDateAfter ) { - r = ( trackDate > referenceDate ) ? 1.0 : exp( factor * ( (double)trackDate - (double)referenceDate ) ); - } else if ( comp == Constraint::CompareDateBefore ) { - r = ( trackDate < referenceDate ) ? 1.0 : exp( factor * ( (double)referenceDate - (double)trackDate ) ); - } else { - r = 0.0; - } - - return r; -} - -double -ConstraintTypes::TagMatch::labelComparison( Meta::TrackPtr t ) const -{ - Meta::LabelList labelList = t->labels(); - - double v = 0.0; - foreach ( Meta::LabelPtr label, labelList ) { - // this is correct ... - // v = qMax( compare( label, m_comparison, m_value.toString() ), v ); - - // ... but as long as compare() returns only 0.0 or 1.0, the following is faster: - v = compare( label->prettyName(), m_comparison, m_value.toString() ); - if ( v == 1.0 ) { - return 1.0; - } - } - - return v; -} QString ConstraintTypes::TagMatch::comparisonToString() const { if ( m_fieldsModel->type_of( m_field ) == FieldTypeInt ) { - if ( m_comparison == Constraint::CompareNumEquals ) { + if ( m_comparison == CompareNumEquals ) { return QString( i18nc("a numerical tag (like year or track number) equals a value","equals") ); - } else if ( m_comparison == Constraint::CompareNumGreaterThan ) { + } else if ( m_comparison == CompareNumGreaterThan ) { return QString( i18n("greater than") ); - } else if ( m_comparison == Constraint::CompareNumLessThan ) { + } else if ( m_comparison == CompareNumLessThan ) { return QString( i18n("less than") ); } } else if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) { - if ( m_comparison == Constraint::CompareDateBefore ) { + if ( m_comparison == CompareDateBefore ) { return QString( i18n("before") ); - } else if ( m_comparison == Constraint::CompareDateOn ) { + } else if ( m_comparison == CompareDateOn ) { return QString( i18n("on") ); - } else if ( m_comparison == Constraint::CompareDateAfter ) { + } else if ( m_comparison == CompareDateAfter ) { return QString( i18n("after") ); - } else if ( m_comparison == Constraint::CompareDateWithin ) { + } else if ( m_comparison == CompareDateWithin ) { return QString( i18n("within") ); } } else { - if ( m_comparison == Constraint::CompareStrEquals ) { +#if 0 + // FIXME: Replace the block below with this one after string freeze is lifted + if ( m_comparison == CompareStrEquals ) { + return QString( i18nc("an alphabetical tag (like title or artist name) equals some string","equals") ); + } else if ( m_comparison == CompareStrStartsWith ) { + return QString( i18nc("an alphabetical tag (like title or artist name) starts with some string","starts with") ); + } else if ( m_comparison == CompareStrEndsWith ) { + return QString( i18nc("an alphabetical tag (like title or artist name) ends with some string","ends with") ); + } else if ( m_comparison == CompareStrContains ) { + return QString( i18nc("an alphabetical tag (like title or artist name) contains some string","contains") ); + } else if ( m_comparison == CompareStrRegExp ) { + return QString( i18n("regexp") ); + } +#endif + if ( m_comparison == CompareStrEquals ) { return QString( i18nc("an alphabetical tag (like title or artist name) equals some string","equals") ); - } else if ( m_comparison == Constraint::CompareStrStartsWith ) { + } else if ( m_comparison == CompareStrStartsWith ) { return QString( i18n("starts with") ); - } else if ( m_comparison == Constraint::CompareStrEndsWith ) { + } else if ( m_comparison == CompareStrEndsWith ) { return QString( i18n("ends with") ); - } else if ( m_comparison == Constraint::CompareStrContains ) { + } else if ( m_comparison == CompareStrContains ) { return QString( i18n("contains") ); - } else if ( m_comparison == Constraint::CompareStrRegExp ) { + } else if ( m_comparison == CompareStrRegExp ) { return QString( i18n("regexp") ); } } @@ -541,7 +485,7 @@ QString ConstraintTypes::TagMatch::valueToString() const { if ( m_fieldsModel->type_of( m_field ) == FieldTypeDate ) { - if ( m_comparison != Constraint::CompareDateWithin ) { + if ( m_comparison != CompareDateWithin ) { return m_value.toDate().toString( Qt::ISODate ); } else { QString unit; @@ -570,70 +514,67 @@ ConstraintTypes::TagMatch::matches( Meta::TrackPtr track ) const { if ( !m_matchCache.contains( track ) ) { double v = 0.0; - int lengthInSec, targetInSec; // these are used for track length calculations below - switch ( m_fieldsModel->meta_value_of( m_field ) ) { + qint64 fmv = m_fieldsModel->meta_value_of( m_field ); + switch ( fmv ) { case Meta::valUrl: - v = compare( track->prettyUrl(), m_comparison, m_value.toString() ); + v = m_comparer->compareStr( track->prettyUrl(), m_comparison, m_value.toString() ); break; case Meta::valTitle: - v = compare( track->prettyName(), m_comparison, m_value.toString() ); + v = m_comparer->compareStr( track->prettyName(), m_comparison, m_value.toString() ); break; case Meta::valArtist: - v = compare( track->artist()->prettyName(), m_comparison, m_value.toString() ); + v = m_comparer->compareStr( track->artist()->prettyName(), m_comparison, m_value.toString() ); break; case Meta::valAlbum: - v = compare( track->album()->prettyName(), m_comparison, m_value.toString() ); + v = m_comparer->compareStr( track->album()->prettyName(), m_comparison, m_value.toString() ); break; case Meta::valGenre: - v = compare( track->genre()->prettyName(), m_comparison, m_value.toString() ); + v = m_comparer->compareStr( track->genre()->prettyName(), m_comparison, m_value.toString() ); break; case Meta::valComposer: - v = compare( track->composer()->prettyName(), m_comparison, m_value.toString() ); + v = m_comparer->compareStr( track->composer()->prettyName(), m_comparison, m_value.toString() ); break; case Meta::valYear: - v = compare<int>( track->year()->prettyName().toInt(), m_comparison, m_value.toInt() ); + v = m_comparer->compareNum( track->year()->prettyName().toInt(), m_comparison, m_value.toInt(), m_strictness, fmv ); break; case Meta::valComment: - v = compare( track->comment(), m_comparison, m_value.toString() ); + v = m_comparer->compareStr( track->comment(), m_comparison, m_value.toString() ); break; case Meta::valTrackNr: - v = compare<int>( track->trackNumber(), m_comparison, m_value.toInt(), m_strictness ); + v = m_comparer->compareNum( track->trackNumber(), m_comparison, m_value.toInt(), m_strictness, fmv ); break; case Meta::valDiscNr: - v = compare<int>( track->discNumber(), m_comparison, m_value.toInt(), m_strictness ); + v = m_comparer->compareNum( track->discNumber(), m_comparison, m_value.toInt(), m_strictness, fmv ); break; case Meta::valLength: - // the strictness factor doesn't handle milliseconds very well - lengthInSec = track->length() / 1000; - targetInSec = m_value.toInt() / 1000; - v = compare<int>( lengthInSec, m_comparison, targetInSec, m_strictness ); + v = m_comparer->compareNum( track->length(), m_comparison, m_value.toInt(), m_strictness, fmv ); break; case Meta::valBitrate: - v = compare<int>( track->bitrate(), m_comparison, m_value.toInt(), m_strictness ); + v = m_comparer->compareNum( track->bitrate(), m_comparison, m_value.toInt(), m_strictness, fmv ); break; case Meta::valFilesize: - v = compare<int>( track->filesize(), m_comparison, m_value.toInt(), m_strictness ); + v = m_comparer->compareNum( track->filesize(), m_comparison, m_value.toInt(), m_strictness, fmv ); break; case Meta::valCreateDate: - v = dateComparison( track->createDate().toTime_t() ); + v = m_comparer->compareDate( track->createDate().toTime_t(), m_comparison, m_value, m_strictness ); break; case Meta::valScore: - v = compare<double>( track->score(), m_comparison, m_value.toDouble(), m_strictness ); + v = m_comparer->compareNum( track->score(), m_comparison, m_value.toDouble(), m_strictness, fmv ); break; case Meta::valRating: - v = compare<int>( track->rating(), m_comparison, m_value.toInt(), m_strictness ); + v = m_comparer->compareNum( track->rating(), m_comparison, m_value.toInt(), m_strictness, fmv ); break; case Meta::valFirstPlayed: - v = dateComparison( track->firstPlayed() ); + v = m_comparer->compareDate( track->firstPlayed(), m_comparison, m_value, m_strictness ); break; case Meta::valLastPlayed: - v = dateComparison( track->lastPlayed() ); + v = m_comparer->compareDate( track->lastPlayed(), m_comparison, m_value, m_strictness ); break; case Meta::valPlaycount: - v = compare<int>( track->playCount(), m_comparison, m_value.toInt(), m_strictness ); + v = m_comparer->compareNum( track->playCount(), m_comparison, m_value.toInt(), m_strictness, fmv ); break; case Meta::valLabel: - v = labelComparison( track ); + v = m_comparer->compareLabels( track, m_comparison, m_value.toString() ); break; default: v = 0.0; @@ -733,7 +674,7 @@ ConstraintTypes::TagMatchEditWidget::TagMatchEditWidget( } else if ( m_fieldsModel->type_of( field ) == TagMatch::FieldTypeDate ) { ui.comboBox_ComparisonDate->setCurrentIndex( comparison ); ui.slider_StrictnessDate->setValue( strictness ); - if ( comparison == Constraint::CompareDateWithin ) { + if ( comparison == TagMatch::CompareDateWithin ) { ui.stackedWidget_Date->setCurrentIndex( 1 ); ui.spinBox_ValueDateValue->setValue( value.value<DateRange>().first ); ui.comboBox_ValueDateUnit->setCurrentIndex( value.value<DateRange>().second ); @@ -759,7 +700,7 @@ ConstraintTypes::TagMatchEditWidget::~TagMatchEditWidget() void ConstraintTypes::TagMatchEditWidget::on_comboBox_ComparisonDate_currentIndexChanged( int c ) { - if ( c == Constraint::CompareDateWithin ) + if ( c == TagMatch::CompareDateWithin ) ui.stackedWidget_Date->setCurrentIndex( 1 ); else ui.stackedWidget_Date->setCurrentIndex( 0 ); @@ -818,7 +759,7 @@ ConstraintTypes::TagMatchEditWidget::on_comboBox_Field_currentIndexChanged( int ui.stackedWidget_Field->setCurrentIndex( 1 ); c = ui.comboBox_ComparisonDate->currentIndex(); s = ui.slider_StrictnessDate->value(); - if ( c == Constraint::CompareDateWithin ) { + if ( c == TagMatch::CompareDateWithin ) { ui.stackedWidget_Date->setCurrentIndex( 1 ); int a = ui.spinBox_ValueDateValue->value(); int b = ui.comboBox_ValueDateUnit->currentIndex(); diff --git a/src/playlistgenerator/constraints/TagMatch.h b/src/playlistgenerator/constraints/TagMatch.h index dfa2c35..094a68b 100644 --- a/src/playlistgenerator/constraints/TagMatch.h +++ b/src/playlistgenerator/constraints/TagMatch.h @@ -49,8 +49,14 @@ namespace ConstraintTypes { class TagMatch : public MatchingConstraint { Q_OBJECT + /* support classes declared below */ + class Comparer; + public: enum FieldTypes { FieldTypeInt, FieldTypeDate, FieldTypeString }; + enum NumComparison { CompareNumLessThan, CompareNumEquals, CompareNumGreaterThan }; + enum StrComparison { CompareStrEquals, CompareStrStartsWith, CompareStrEndsWith, CompareStrContains, CompareStrRegExp }; + enum DateComparison { CompareDateBefore, CompareDateOn, CompareDateAfter, CompareDateWithin }; static Constraint* createFromXml(QDomElement&, ConstraintNode*); static Constraint* createNew(ConstraintNode*); @@ -74,6 +80,10 @@ namespace ConstraintTypes { virtual ConstraintNode::Vote* vote( const Meta::TrackList&, const Meta::TrackList& ) const; +#ifndef KDE_NO_DEBUG_OUTPUT + virtual void audit( const Meta::TrackList& ) const; +#endif + // Implementation of MatchingConstraint virtuals const QBitArray whatTracksMatch( const Meta::TrackList& ); int constraintMatchType() const; @@ -97,20 +107,69 @@ namespace ConstraintTypes { double m_strictness; QVariant m_value; - // convenience class + // convenience classes + const Comparer* const m_comparer; const TagMatchFieldsModel* const m_fieldsModel; // internal state data double m_satisfaction; // convenience functions - double dateComparison( uint ) const; - double labelComparison( Meta::TrackPtr ) const; QString comparisonToString() const; QString valueToString() const; bool matches( const Meta::TrackPtr ) const; // match values are fuzzily calculated mutable QHash<Meta::TrackPtr, bool> m_matchCache; // internal cache for per-track true/false data + + + /* support class that does fuzzy comparisons */ + class Comparer { + public: + Comparer(); + ~Comparer(); + + double compareNum( const double, const int, const double, const double, const qint64 ) const; + double compareStr( const QString&, const int, const QString& ) const; + double compareDate( const uint, const int, const QVariant&, const double ) const; + double compareLabels( const Meta::TrackPtr, const int, const QString& ) const; + + // rough inverses of the comparison + uint rangeDate( const double ) const; + int rangeNum( const double, const qint64 ) const; + + private: + QHash<qint64, double> m_numFieldWeight; + const double m_dateWeight; + + double fuzzyProb( const double, const double, const double, const double ) const; + }; + + }; + + /* support class that manages data relationships for the various fields */ + class TagMatchFieldsModel : public QAbstractListModel { + Q_OBJECT + + public: + TagMatchFieldsModel(); + ~TagMatchFieldsModel(); + + // required by QAbstractListModel + QVariant data( const QModelIndex&, int role = Qt::DisplayRole ) const; + int rowCount( const QModelIndex& parent = QModelIndex() ) const; + + bool contains( const QString& ) const; + int index_of( const QString& ) const; + QString field_at( int ) const; + qint64 meta_value_of( const QString& ) const; + QString pretty_name_of( const QString& ) const; + TagMatch::FieldTypes type_of( const QString& ) const; + + private: + QList<QString> m_fieldNames; + QHash<QString, TagMatch::FieldTypes> m_fieldTypes; + QHash<QString, qint64> m_fieldMetaValues; + QHash<QString, QString> m_fieldPrettyNames; }; class TagMatchEditWidget : public QWidget { @@ -156,38 +215,13 @@ namespace ConstraintTypes { void on_lineEdit_StringValue_textChanged( const QString& ); void on_rating_RatingValue_ratingChanged( int ); void on_timeEdit_TimeValue_timeChanged( const QTime& ); - void slotUpdateComboBoxLabels( int ); + void slotUpdateComboBoxLabels( int ); private: Ui::TagMatchEditWidget ui; TagMatchFieldsModel* const m_fieldsModel; }; - class TagMatchFieldsModel : public QAbstractListModel { - Q_OBJECT - - public: - TagMatchFieldsModel(); - ~TagMatchFieldsModel(); - - // required by QAbstractListModel - QVariant data( const QModelIndex&, int role = Qt::DisplayRole ) const; - int rowCount( const QModelIndex& parent = QModelIndex() ) const; - - bool contains( const QString& ) const; - int index_of( const QString& ) const; - QString field_at( int ) const; - qint64 meta_value_of( const QString& ) const; - QString pretty_name_of( const QString& ) const; - TagMatch::FieldTypes type_of( const QString& ) const; - - private: - QList<QString> m_fieldNames; - QHash<QString, TagMatch::FieldTypes> m_fieldTypes; - QHash<QString, qint64> m_fieldMetaValues; - QHash<QString, QString> m_fieldPrettyNames; - }; - } // namespace ConstraintTypes typedef QPair<int,int> DateRange; diff --git a/src/playlistgenerator/constraints/TagMatchEditWidget.ui b/src/playlistgenerator/constraints/TagMatchEditWidget.ui index 06d0fe6..2f7fc0a 100644 --- a/src/playlistgenerator/constraints/TagMatchEditWidget.ui +++ b/src/playlistgenerator/constraints/TagMatchEditWidget.ui @@ -306,7 +306,11 @@ p, li { white-space: pre-wrap; } <item> <layout class="QHBoxLayout" name="horizontalLayout_6"> <item> - <widget class="KIntSpinBox" name="spinBox_ValueDateValue"/> + <widget class="KIntSpinBox" name="spinBox_ValueDateValue"> + <property name="maximum"> + <number>999</number> + </property> + </widget> </item> <item> <widget class="QComboBox" name="comboBox_ValueDateUnit"/> diff --git a/src/playlistgenerator/constraints/TagMatchFieldsModel.cpp b/src/playlistgenerator/constraints/TagMatchFieldsModel.cpp deleted file mode 100644 index fd3cf74..0000000 --- a/src/playlistgenerator/constraints/TagMatchFieldsModel.cpp +++ /dev/null @@ -1,166 +0,0 @@ -/**************************************************************************************** - * Copyright (c) 2010 Soren Harward <stharward@gmail.com> * - * * - * This program is free software; you can redistribute it and/or modify it under * - * the terms of the GNU 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 General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License along with * - * this program. If not, see <http://www.gnu.org/licenses/>. * - ****************************************************************************************/ - -#include "TagMatch.h" - -#include "core/meta/support/MetaConstants.h" - -ConstraintTypes::TagMatchFieldsModel::TagMatchFieldsModel() -{ - // if you add something here, you need to update TagMatch::matches() to handle it - m_fieldNames << "url" - << "title" - << "artist name" - << "album name" - << "genre" - << "composer" - << "year" - << "comment" - << "track number" - << "disc number" - << "length" - << "score" - << "rating" - << "create date" - << "first played" - << "last played" - << "play count" - << "label"; - - m_fieldTypes.insert( "url", TagMatch::FieldTypeString ); - m_fieldTypes.insert( "title", TagMatch::FieldTypeString ); - m_fieldTypes.insert( "artist name", TagMatch::FieldTypeString ); - m_fieldTypes.insert( "album name", TagMatch::FieldTypeString ); - m_fieldTypes.insert( "genre", TagMatch::FieldTypeString ); - m_fieldTypes.insert( "composer", TagMatch::FieldTypeString ); - m_fieldTypes.insert( "year", TagMatch::FieldTypeInt ); - m_fieldTypes.insert( "comment", TagMatch::FieldTypeString ); - m_fieldTypes.insert( "track number", TagMatch::FieldTypeInt ); - m_fieldTypes.insert( "disc number", TagMatch::FieldTypeInt ); - m_fieldTypes.insert( "length", TagMatch::FieldTypeInt ); - m_fieldTypes.insert( "create date", TagMatch::FieldTypeDate); - m_fieldTypes.insert( "score", TagMatch::FieldTypeInt ); - m_fieldTypes.insert( "rating", TagMatch::FieldTypeInt ); - m_fieldTypes.insert( "first played", TagMatch::FieldTypeDate ); - m_fieldTypes.insert( "last played", TagMatch::FieldTypeDate ); - m_fieldTypes.insert( "play count", TagMatch::FieldTypeInt ); - m_fieldTypes.insert( "label", TagMatch::FieldTypeString ); - - m_fieldMetaValues.insert( "url", Meta::valUrl ); - m_fieldMetaValues.insert( "title", Meta::valTitle ); - m_fieldMetaValues.insert( "artist name", Meta::valArtist ); - m_fieldMetaValues.insert( "album name", Meta::valAlbum ); - m_fieldMetaValues.insert( "genre", Meta::valGenre ); - m_fieldMetaValues.insert( "composer", Meta::valComposer ); - m_fieldMetaValues.insert( "year", Meta::valYear ); - m_fieldMetaValues.insert( "comment", Meta::valComment ); - m_fieldMetaValues.insert( "track number", Meta::valTrackNr ); - m_fieldMetaValues.insert( "disc number", Meta::valDiscNr ); - m_fieldMetaValues.insert( "length", Meta::valLength ); - m_fieldMetaValues.insert( "create date", Meta::valCreateDate); - m_fieldMetaValues.insert( "score", Meta::valScore ); - m_fieldMetaValues.insert( "rating", Meta::valRating ); - m_fieldMetaValues.insert( "first played", Meta::valFirstPlayed ); - m_fieldMetaValues.insert( "last played", Meta::valLastPlayed ); - m_fieldMetaValues.insert( "play count", Meta::valPlaycount ); - m_fieldMetaValues.insert( "label", Meta::valLabel ); - m_fieldMetaValues.insert( "url", Meta::valUrl ); - - m_fieldPrettyNames.insert( "url", i18n("url") ); - m_fieldPrettyNames.insert( "title", i18n("title") ); - m_fieldPrettyNames.insert( "artist name", i18n("artist name") ); - m_fieldPrettyNames.insert( "album name", i18n("album name") ); - m_fieldPrettyNames.insert( "genre", i18n("genre") ); - m_fieldPrettyNames.insert( "composer", i18n("composer") ); - m_fieldPrettyNames.insert( "year", i18n("year") ); - m_fieldPrettyNames.insert( "comment", i18n("comment") ); - m_fieldPrettyNames.insert( "track number", i18n("track number") ); - m_fieldPrettyNames.insert( "disc number", i18n("disc number") ); - m_fieldPrettyNames.insert( "length", i18n("length") ); - m_fieldPrettyNames.insert( "create date", i18n("added to collection") ); - m_fieldPrettyNames.insert( "score", i18n("score") ); - m_fieldPrettyNames.insert( "rating", i18n("rating") ); - m_fieldPrettyNames.insert( "first played", i18n("first played") ); - m_fieldPrettyNames.insert( "last played", i18n("last played") ); - m_fieldPrettyNames.insert( "play count", i18n("play count") ); - m_fieldPrettyNames.insert( "label", i18n("label") ); -} - -ConstraintTypes::TagMatchFieldsModel::~TagMatchFieldsModel() -{ -} - -int -ConstraintTypes::TagMatchFieldsModel::rowCount( const QModelIndex& parent ) const -{ - Q_UNUSED( parent ) - return m_fieldNames.length(); -} - -QVariant -ConstraintTypes::TagMatchFieldsModel::data( const QModelIndex& idx, int role ) const -{ - QString s = m_fieldNames.at( idx.row() ); - - switch ( role ) { - case Qt::DisplayRole: - case Qt::EditRole: - return QVariant( m_fieldPrettyNames.value( s ) ); - break; - default: - return QVariant(); - } - return QVariant(); -} - -bool -ConstraintTypes::TagMatchFieldsModel::contains( const QString& s ) const -{ - return m_fieldNames.contains( s ); -} - -int -ConstraintTypes::TagMatchFieldsModel::index_of( const QString& s ) const -{ - return m_fieldNames.indexOf( s ); -} - -QString -ConstraintTypes::TagMatchFieldsModel::field_at( int idx ) const -{ - if ( ( idx >= 0 ) && ( idx < m_fieldNames.length() ) ) - return m_fieldNames.at( idx ); - else - return QString(); -} - -qint64 -ConstraintTypes::TagMatchFieldsModel::meta_value_of( const QString& f ) const -{ - return m_fieldMetaValues.value( f ); -} - -QString -ConstraintTypes::TagMatchFieldsModel::pretty_name_of( const QString& f ) const -{ - return m_fieldPrettyNames.value( f ); -} - -ConstraintTypes::TagMatch::FieldTypes -ConstraintTypes::TagMatchFieldsModel::type_of( const QString& f ) const -{ - return m_fieldTypes.value( f ); -} diff --git a/src/playlistgenerator/constraints/TagMatchSupport.cpp b/src/playlistgenerator/constraints/TagMatchSupport.cpp new file mode 100644 index 0000000..297c589 --- /dev/null +++ b/src/playlistgenerator/constraints/TagMatchSupport.cpp @@ -0,0 +1,334 @@ +/**************************************************************************************** + * Copyright (c) 2010 Soren Harward <stharward@gmail.com> * + * * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along with * + * this program. If not, see <http://www.gnu.org/licenses/>. * + ****************************************************************************************/ + +#define DEBUG_PREFIX "Constraint::TagMatchSupport" + +#include "TagMatch.h" + +#include "core/meta/Meta.h" +#include "core/meta/support/MetaConstants.h" +#include "core/support/Debug.h" + +#include <math.h> + +ConstraintTypes::TagMatchFieldsModel::TagMatchFieldsModel() +{ + m_fieldNames << "url" + << "title" + << "artist name" + << "album name" + << "genre" + << "composer" + << "year" + << "comment" + << "track number" + << "disc number" + << "length" + << "score" + << "rating" + << "create date" + << "first played" + << "last played" + << "play count" + << "label"; + + m_fieldTypes.insert( "url", TagMatch::FieldTypeString ); + m_fieldTypes.insert( "title", TagMatch::FieldTypeString ); + m_fieldTypes.insert( "artist name", TagMatch::FieldTypeString ); + m_fieldTypes.insert( "album name", TagMatch::FieldTypeString ); + m_fieldTypes.insert( "genre", TagMatch::FieldTypeString ); + m_fieldTypes.insert( "composer", TagMatch::FieldTypeString ); + m_fieldTypes.insert( "year", TagMatch::FieldTypeInt ); + m_fieldTypes.insert( "comment", TagMatch::FieldTypeString ); + m_fieldTypes.insert( "track number", TagMatch::FieldTypeInt ); + m_fieldTypes.insert( "disc number", TagMatch::FieldTypeInt ); + m_fieldTypes.insert( "length", TagMatch::FieldTypeInt ); + m_fieldTypes.insert( "create date", TagMatch::FieldTypeDate); + m_fieldTypes.insert( "score", TagMatch::FieldTypeInt ); + m_fieldTypes.insert( "rating", TagMatch::FieldTypeInt ); + m_fieldTypes.insert( "first played", TagMatch::FieldTypeDate ); + m_fieldTypes.insert( "last played", TagMatch::FieldTypeDate ); + m_fieldTypes.insert( "play count", TagMatch::FieldTypeInt ); + m_fieldTypes.insert( "label", TagMatch::FieldTypeString ); + + m_fieldMetaValues.insert( "url", Meta::valUrl ); + m_fieldMetaValues.insert( "title", Meta::valTitle ); + m_fieldMetaValues.insert( "artist name", Meta::valArtist ); + m_fieldMetaValues.insert( "album name", Meta::valAlbum ); + m_fieldMetaValues.insert( "genre", Meta::valGenre ); + m_fieldMetaValues.insert( "composer", Meta::valComposer ); + m_fieldMetaValues.insert( "year", Meta::valYear ); + m_fieldMetaValues.insert( "comment", Meta::valComment ); + m_fieldMetaValues.insert( "track number", Meta::valTrackNr ); + m_fieldMetaValues.insert( "disc number", Meta::valDiscNr ); + m_fieldMetaValues.insert( "length", Meta::valLength ); + m_fieldMetaValues.insert( "create date", Meta::valCreateDate); + m_fieldMetaValues.insert( "score", Meta::valScore ); + m_fieldMetaValues.insert( "rating", Meta::valRating ); + m_fieldMetaValues.insert( "first played", Meta::valFirstPlayed ); + m_fieldMetaValues.insert( "last played", Meta::valLastPlayed ); + m_fieldMetaValues.insert( "play count", Meta::valPlaycount ); + m_fieldMetaValues.insert( "label", Meta::valLabel ); + + m_fieldPrettyNames.insert( "url", i18n("url") ); + m_fieldPrettyNames.insert( "title", i18n("title") ); + m_fieldPrettyNames.insert( "artist name", i18n("artist name") ); + m_fieldPrettyNames.insert( "album name", i18n("album name") ); + m_fieldPrettyNames.insert( "genre", i18n("genre") ); + m_fieldPrettyNames.insert( "composer", i18n("composer") ); + m_fieldPrettyNames.insert( "year", i18n("year") ); + m_fieldPrettyNames.insert( "comment", i18n("comment") ); + m_fieldPrettyNames.insert( "track number", i18n("track number") ); + m_fieldPrettyNames.insert( "disc number", i18n("disc number") ); + m_fieldPrettyNames.insert( "length", i18n("length") ); + m_fieldPrettyNames.insert( "create date", i18n("added to collection") ); + m_fieldPrettyNames.insert( "score", i18n("score") ); + m_fieldPrettyNames.insert( "rating", i18n("rating") ); + m_fieldPrettyNames.insert( "first played", i18n("first played") ); + m_fieldPrettyNames.insert( "last played", i18n("last played") ); + m_fieldPrettyNames.insert( "play count", i18n("play count") ); + m_fieldPrettyNames.insert( "label", i18n("label") ); +} + +ConstraintTypes::TagMatchFieldsModel::~TagMatchFieldsModel() +{ +} + +int +ConstraintTypes::TagMatchFieldsModel::rowCount( const QModelIndex& parent ) const +{ + Q_UNUSED( parent ) + return m_fieldNames.length(); +} + +QVariant +ConstraintTypes::TagMatchFieldsModel::data( const QModelIndex& idx, int role ) const +{ + QString s = m_fieldNames.at( idx.row() ); + + switch ( role ) { + case Qt::DisplayRole: + case Qt::EditRole: + return QVariant( m_fieldPrettyNames.value( s ) ); + break; + default: + return QVariant(); + } + return QVariant(); +} + +bool +ConstraintTypes::TagMatchFieldsModel::contains( const QString& s ) const +{ + return m_fieldNames.contains( s ); +} + +int +ConstraintTypes::TagMatchFieldsModel::index_of( const QString& s ) const +{ + return m_fieldNames.indexOf( s ); +} + +QString +ConstraintTypes::TagMatchFieldsModel::field_at( int idx ) const +{ + if ( ( idx >= 0 ) && ( idx < m_fieldNames.length() ) ) + return m_fieldNames.at( idx ); + else + return QString(); +} + +qint64 +ConstraintTypes::TagMatchFieldsModel::meta_value_of( const QString& f ) const +{ + return m_fieldMetaValues.value( f ); +} + +QString +ConstraintTypes::TagMatchFieldsModel::pretty_name_of( const QString& f ) const +{ + return m_fieldPrettyNames.value( f ); +} + +ConstraintTypes::TagMatch::FieldTypes +ConstraintTypes::TagMatchFieldsModel::type_of( const QString& f ) const +{ + return m_fieldTypes.value( f ); +} + +/************************************* +**************************************/ + +ConstraintTypes::TagMatch::Comparer::Comparer() : m_dateWeight( 1209600.0 ) +{ + m_numFieldWeight.insert( Meta::valYear, 8.0 ); + m_numFieldWeight.insert( Meta::valTrackNr, 5.0 ); + m_numFieldWeight.insert( Meta::valDiscNr, 0.75 ); + m_numFieldWeight.insert( Meta::valLength, 100000.0 ); + m_numFieldWeight.insert( Meta::valScore, 20.0 ); + m_numFieldWeight.insert( Meta::valRating, 3.0 ); + m_numFieldWeight.insert( Meta::valPlaycount, 4.0 ); +} + +ConstraintTypes::TagMatch::Comparer::~Comparer() +{ +} + +double +ConstraintTypes::TagMatch::Comparer::compareNum( const double test, + const int comparison, + const double target, + const double strictness, + const qint64 field ) const +{ + const double weight = m_numFieldWeight.value( field ); + + if ( comparison == CompareNumEquals ) { + // fuzzy equals -- within 1%, or within 0.001 + if ( ( abs( test - target ) < ( abs( test + target ) / 200.0 ) ) || ( abs( test - target ) < 0.001 ) ) { + return 1.0; + } else { + return fuzzyProb( test, target, strictness, weight ); + } + } else if ( comparison == CompareNumGreaterThan ) { + return ( test > target ) ? 1.0 : fuzzyProb( test, target, strictness, weight ); + } else if ( comparison == CompareNumLessThan ) { + return ( test < target ) ? 1.0 : fuzzyProb( test, target, strictness, weight ); + } else { + return 0.0; + } + return 0.0; +} + +double +ConstraintTypes::TagMatch::Comparer::compareStr( const QString& test, + const int comparison, + const QString& target ) const +{ + if ( comparison == CompareStrEquals ) { + if ( test.compare( target, Qt::CaseInsensitive ) == 0 ) + return 1.0; + } else if ( comparison == CompareStrStartsWith ) { + if ( test.startsWith( target, Qt::CaseInsensitive ) ) + return 1.0; + } else if ( comparison == CompareStrEndsWith ) { + if ( test.endsWith( target, Qt::CaseInsensitive ) ) + return 1.0; + } else if ( comparison == CompareStrContains ) { + if ( test.contains( target, Qt::CaseInsensitive ) ) + return 1.0; + } else if ( comparison == CompareStrRegExp ) { + QRegExp rx( target ); + if ( rx.indexIn( test ) >= 0 ) + return 1.0; + } else { + return 0.0; + } + return 0.0; +} + + +double +ConstraintTypes::TagMatch::Comparer::compareDate( const uint test, + const int comparison, + const QVariant& targetVar, + const double strictness ) const +{ + const double weight = m_dateWeight; + + int comp = comparison; + uint target = 0; + if ( comparison == CompareDateWithin ) { + comp = CompareDateAfter; + QDateTime now = QDateTime::currentDateTime(); + DateRange r = targetVar.value<DateRange>(); + switch ( r.second ) { + case 0: + target = now.addDays( -1 * r.first ).toTime_t(); + break; + case 1: + target = now.addMonths( -1 * r.first ).toTime_t(); + break; + case 2: + target = now.addYears( -1 * r.first ).toTime_t(); + break; + default: + break; + } + } else { + target = targetVar.value<uint>(); + } + + if ( comp == CompareDateOn ) { + // fuzzy equals -- within 1%, or within 10.0 + if ( ( abs( test - target ) < ( abs( test + target ) / 200.0 ) ) || ( abs( test - target ) < 10.0 ) ) { + return 1.0; + } else { + return fuzzyProb( static_cast<double>(test), static_cast<double>(target), strictness, weight ); + } + } else if ( comp == CompareDateAfter ) { + return ( test > target ) ? 1.0 : fuzzyProb( static_cast<double>(test), static_cast<double>(target), strictness, weight ); + } else if ( comp == CompareDateBefore ) { + return ( test < target ) ? 1.0 : fuzzyProb( static_cast<double>(test), static_cast<double>(target), strictness, weight ); + } else { + return 0.0; + } + return 0.0; +} + +double +ConstraintTypes::TagMatch::Comparer::compareLabels( const Meta::TrackPtr t, const int comparison, const QString& target ) const +{ + Meta::LabelList labelList = t->labels(); + + double v = 0.0; + foreach ( Meta::LabelPtr label, labelList ) { + // this is technically more correct ... + // v = qMax( compare( label->prettyName(), comparison, target ), v ); + + // ... but as long as compareStr() returns only 0.0 or 1.0, the following is faster: + v = compareStr( label->prettyName(), comparison, target ); + if ( v > 0.99 ) { + return 1.0; + } + } + + return v; +} + +uint +ConstraintTypes::TagMatch::Comparer::rangeDate( const double strictness ) const +{ + if ( strictness > 0.99 ) return 0; + const double s = strictness * strictness; + return static_cast<uint>( ceil( 0.460517 * m_dateWeight / ( 0.1 + s ) ) ); +} + +int +ConstraintTypes::TagMatch::Comparer::rangeNum( const double strictness, const qint64 field ) const +{ + if ( strictness > 0.99 ) return 0; + const double s = strictness * strictness; + const double w = m_numFieldWeight.value( field ); + return static_cast<int>( ceil( 0.460517 * w / ( 0.1 + s ) ) ); +} + +double +ConstraintTypes::TagMatch::Comparer::fuzzyProb( const double a, const double b, const double strictness, const double w ) const +{ + const double s = strictness * strictness; + return exp( -10.0 * ( 0.1 + s ) / w * ( 1 + abs( a - b ) ) ); +} \ No newline at end of file