File indexing completed on 2024-05-19 04:49:42
0001 /**************************************************************************************** 0002 * Copyright (c) 2008 Daniel Jones <danielcjones@gmail.com> * 0003 * Copyright (c) 2009 Leo Franchi <lfranchi@kde.org> * 0004 * Copyright (c) 2010,2011 Ralf Engels <ralf-engels@gmx.de> * 0005 * * 0006 * This program is free software; you can redistribute it and/or modify it under * 0007 * the terms of the GNU General Public License as published by the Free Software * 0008 * Foundation; either version 2 of the License, or (at your option) version 3 or * 0009 * any later version accepted by the membership of KDE e.V. (or its successor approved * 0010 * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * 0011 * version 3 of the license. * 0012 * * 0013 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0014 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0015 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0016 * * 0017 * You should have received a copy of the GNU General Public License along with * 0018 * this program. If not, see <http://www.gnu.org/licenses/>. * 0019 ****************************************************************************************/ 0020 0021 #define DEBUG_PREFIX "TagMatchBias" 0022 0023 #include "TagMatchBias.h" 0024 0025 #include "core/collections/Collection.h" 0026 #include "core/collections/QueryMaker.h" 0027 #include "core/meta/Meta.h" 0028 #include "core/support/Debug.h" 0029 #include "core-impl/collections/support/CollectionManager.h" 0030 #include "dynamic/TrackSet.h" 0031 0032 #include <QDateTime> 0033 #include <QCheckBox> 0034 #include <QHBoxLayout> 0035 #include <QLabel> 0036 #include <QTimer> 0037 #include <QXmlStreamReader> 0038 #include <QXmlStreamWriter> 0039 0040 QString 0041 Dynamic::TagMatchBiasFactory::i18nName() const 0042 { return i18nc("Name of the \"TagMatch\" bias", "Match meta tag"); } 0043 0044 QString 0045 Dynamic::TagMatchBiasFactory::name() const 0046 { return Dynamic::TagMatchBias::sName(); } 0047 0048 QString 0049 Dynamic::TagMatchBiasFactory::i18nDescription() const 0050 { return i18nc("Description of the \"TagMatch\" bias", 0051 "The \"TagMatch\" bias adds tracks that\n" 0052 "fulfill a specific condition."); } 0053 0054 Dynamic::BiasPtr 0055 Dynamic::TagMatchBiasFactory::createBias() 0056 { return Dynamic::BiasPtr( new Dynamic::TagMatchBias() ); } 0057 0058 0059 // ----- SimpleMatchBias -------- 0060 0061 Dynamic::SimpleMatchBias::SimpleMatchBias() 0062 : m_invert( false ) 0063 { } 0064 0065 void 0066 Dynamic::SimpleMatchBias::fromXml( QXmlStreamReader *reader ) 0067 { 0068 m_invert = reader->attributes().value( QStringLiteral("invert") ).toString().toInt(); 0069 } 0070 0071 void 0072 Dynamic::SimpleMatchBias::toXml( QXmlStreamWriter *writer ) const 0073 { 0074 if( m_invert ) 0075 writer->writeAttribute(QStringLiteral("invert"), QStringLiteral("1")); 0076 } 0077 0078 bool 0079 Dynamic::SimpleMatchBias::isInvert() const 0080 { 0081 return m_invert; 0082 } 0083 0084 void 0085 Dynamic::SimpleMatchBias::setInvert( bool value ) 0086 { 0087 DEBUG_BLOCK; 0088 if( value == m_invert ) 0089 return; 0090 0091 m_invert = value; 0092 // setting "invert" does not invalidate the search results 0093 Q_EMIT changed( BiasPtr(this) ); 0094 } 0095 0096 0097 Dynamic::TrackSet 0098 Dynamic::SimpleMatchBias::matchingTracks( const Meta::TrackList& playlist, 0099 int contextCount, int finalCount, 0100 const Dynamic::TrackCollectionPtr &universe ) const 0101 { 0102 Q_UNUSED( playlist ); 0103 Q_UNUSED( contextCount ); 0104 Q_UNUSED( finalCount ); 0105 0106 if( tracksValid() ) 0107 return m_tracks; 0108 0109 m_tracks = Dynamic::TrackSet( universe, m_invert ); 0110 0111 QTimer::singleShot(0, 0112 const_cast<SimpleMatchBias*>(this), 0113 &SimpleMatchBias::newQuery); // create the new query from my parent thread 0114 0115 return Dynamic::TrackSet(); 0116 } 0117 0118 void 0119 Dynamic::SimpleMatchBias::updateReady( const QStringList &uids ) 0120 { 0121 if( m_invert ) 0122 m_tracks.subtract( uids ); 0123 else 0124 m_tracks.unite( uids ); 0125 } 0126 0127 void 0128 Dynamic::SimpleMatchBias::updateFinished() 0129 { 0130 m_tracksTime = QDateTime::currentDateTime(); 0131 m_qm.reset(); 0132 debug() << "SimpleMatchBias::"<<name()<<"updateFinished"<<m_tracks.trackCount(); 0133 Q_EMIT resultReady( m_tracks ); 0134 } 0135 0136 bool 0137 Dynamic::SimpleMatchBias::tracksValid() const 0138 { 0139 return m_tracksTime.isValid() && 0140 m_tracksTime.secsTo(QDateTime::currentDateTime()) < 60 * 3; 0141 } 0142 0143 bool 0144 Dynamic::SimpleMatchBias::trackMatches( int position, 0145 const Meta::TrackList& playlist, 0146 int contextCount ) const 0147 { 0148 Q_UNUSED( contextCount ); 0149 if( tracksValid() ) 0150 return m_tracks.contains( playlist.at(position) ); 0151 return true; // we should have already received the tracks before some-one calls trackMatches 0152 } 0153 0154 0155 void 0156 Dynamic::SimpleMatchBias::invalidate() 0157 { 0158 m_tracksTime = QDateTime(); 0159 m_tracks = TrackSet(); 0160 // TODO: need to finish a running query 0161 m_qm.reset(); 0162 } 0163 0164 // ---------- TagMatchBias -------- 0165 0166 0167 Dynamic::TagMatchBiasWidget::TagMatchBiasWidget( Dynamic::TagMatchBias* bias, 0168 QWidget* parent ) 0169 : QWidget( parent ) 0170 , m_bias( bias ) 0171 { 0172 QVBoxLayout *layout = new QVBoxLayout( this ); 0173 0174 QHBoxLayout *invertLayout = new QHBoxLayout(); 0175 m_invertBox = new QCheckBox(); 0176 QLabel *label = new QLabel( i18n("Invert condition") ); 0177 label->setAlignment( Qt::AlignLeft | Qt::AlignVCenter ); 0178 label->setBuddy( m_invertBox ); 0179 invertLayout->addWidget( m_invertBox, 0 ); 0180 invertLayout->addWidget( label, 1 ); 0181 layout->addLayout(invertLayout); 0182 0183 m_queryWidget = new MetaQueryWidget(); 0184 layout->addWidget( m_queryWidget ); 0185 0186 syncControlsToBias(); 0187 0188 connect( m_invertBox, &QCheckBox::toggled, 0189 this, &TagMatchBiasWidget::syncBiasToControls ); 0190 connect( m_queryWidget, &MetaQueryWidget::changed, 0191 this, &TagMatchBiasWidget::syncBiasToControls ); 0192 } 0193 0194 void 0195 Dynamic::TagMatchBiasWidget::syncControlsToBias() 0196 { 0197 m_queryWidget->setFilter( m_bias->filter() ); 0198 m_invertBox->setChecked( m_bias->isInvert() ); 0199 } 0200 0201 void 0202 Dynamic::TagMatchBiasWidget::syncBiasToControls() 0203 { 0204 m_bias->setFilter( m_queryWidget->filter() ); 0205 m_bias->setInvert( m_invertBox->isChecked() ); 0206 } 0207 0208 0209 0210 // ----- TagMatchBias -------- 0211 0212 Dynamic::TagMatchBias::TagMatchBias() 0213 : SimpleMatchBias() 0214 { } 0215 0216 void 0217 Dynamic::TagMatchBias::fromXml( QXmlStreamReader *reader ) 0218 { 0219 SimpleMatchBias::fromXml( reader ); 0220 while (!reader->atEnd()) { 0221 reader->readNext(); 0222 0223 if( reader->isStartElement() ) 0224 { 0225 QStringRef name = reader->name(); 0226 if( name == "field" ) 0227 m_filter.setField( Meta::fieldForPlaylistName( reader->readElementText(QXmlStreamReader::SkipChildElements) ) ); 0228 else if( name == "numValue" ) 0229 m_filter.numValue = reader->readElementText(QXmlStreamReader::SkipChildElements).toUInt(); 0230 else if( name == "numValue2" ) 0231 m_filter.numValue2 = reader->readElementText(QXmlStreamReader::SkipChildElements).toUInt(); 0232 else if( name == "value" ) 0233 m_filter.value = reader->readElementText(QXmlStreamReader::SkipChildElements); 0234 else if( name == "condition" ) 0235 m_filter.condition = conditionForName( reader->readElementText(QXmlStreamReader::SkipChildElements) ); 0236 else 0237 { 0238 debug()<<"Unexpected xml start element"<<reader->name()<<"in input"; 0239 reader->skipCurrentElement(); 0240 } 0241 } 0242 else if( reader->isEndElement() ) 0243 { 0244 break; 0245 } 0246 } 0247 } 0248 0249 void 0250 Dynamic::TagMatchBias::toXml( QXmlStreamWriter *writer ) const 0251 { 0252 SimpleMatchBias::toXml( writer ); 0253 writer->writeTextElement( QStringLiteral("field"), Meta::playlistNameForField( m_filter.field() ) ); 0254 0255 if( m_filter.isNumeric() ) 0256 { 0257 writer->writeTextElement( QStringLiteral("numValue"), QString::number( m_filter.numValue ) ); 0258 writer->writeTextElement( QStringLiteral("numValue2"), QString::number( m_filter.numValue2 ) ); 0259 } 0260 else 0261 { 0262 writer->writeTextElement( QStringLiteral("value"), m_filter.value ); 0263 } 0264 0265 writer->writeTextElement( QStringLiteral("condition"), nameForCondition( m_filter.condition ) ); 0266 } 0267 0268 QString 0269 Dynamic::TagMatchBias::sName() 0270 { 0271 return QStringLiteral( "tagMatchBias" ); 0272 } 0273 0274 QString 0275 Dynamic::TagMatchBias::name() const 0276 { 0277 return Dynamic::TagMatchBias::sName(); 0278 } 0279 0280 QString 0281 Dynamic::TagMatchBias::toString() const 0282 { 0283 if( isInvert() ) 0284 return i18nc("Inverted condition in tag match bias", 0285 "Not %1", m_filter.toString() ); 0286 else 0287 return m_filter.toString(); 0288 } 0289 0290 QWidget* 0291 Dynamic::TagMatchBias::widget( QWidget* parent ) 0292 { 0293 return new Dynamic::TagMatchBiasWidget( this, parent ); 0294 } 0295 0296 bool 0297 Dynamic::TagMatchBias::trackMatches( int position, 0298 const Meta::TrackList& playlist, 0299 int contextCount ) const 0300 { 0301 Q_UNUSED( contextCount ); 0302 if( tracksValid() ) 0303 return m_tracks.contains( playlist.at(position) ); 0304 else 0305 return matches( playlist.at(position) ); 0306 } 0307 0308 MetaQueryWidget::Filter 0309 Dynamic::TagMatchBias::filter() const 0310 { 0311 return m_filter; 0312 } 0313 0314 void 0315 Dynamic::TagMatchBias::setFilter( const MetaQueryWidget::Filter &filter) 0316 { 0317 DEBUG_BLOCK; 0318 m_filter = filter; 0319 invalidate(); 0320 Q_EMIT changed( BiasPtr(this) ); 0321 } 0322 0323 void 0324 Dynamic::TagMatchBias::newQuery() 0325 { 0326 DEBUG_BLOCK; 0327 0328 // ok, I need a new query maker 0329 m_qm.reset( CollectionManager::instance()->queryMaker() ); 0330 0331 // -- set the querymaker 0332 if( m_filter.isDate() ) 0333 { 0334 switch( m_filter.condition ) 0335 { 0336 case MetaQueryWidget::LessThan: 0337 case MetaQueryWidget::Equals: 0338 case MetaQueryWidget::GreaterThan: 0339 m_qm->addNumberFilter( m_filter.field(), m_filter.numValue, 0340 (Collections::QueryMaker::NumberComparison)m_filter.condition ); 0341 break; 0342 case MetaQueryWidget::Between: 0343 m_qm->beginAnd(); 0344 m_qm->addNumberFilter( m_filter.field(), qMin(m_filter.numValue, m_filter.numValue2)-1, 0345 Collections::QueryMaker::GreaterThan ); 0346 m_qm->addNumberFilter( m_filter.field(), qMax(m_filter.numValue, m_filter.numValue2)+1, 0347 Collections::QueryMaker::LessThan ); 0348 m_qm->endAndOr(); 0349 break; 0350 case MetaQueryWidget::OlderThan: 0351 m_qm->addNumberFilter( m_filter.field(), QDateTime::currentDateTimeUtc().toSecsSinceEpoch() - m_filter.numValue, 0352 Collections::QueryMaker::LessThan ); 0353 break; 0354 default: 0355 ; 0356 } 0357 } 0358 else if( m_filter.isNumeric() ) 0359 { 0360 switch( m_filter.condition ) 0361 { 0362 case MetaQueryWidget::LessThan: 0363 case MetaQueryWidget::Equals: 0364 case MetaQueryWidget::GreaterThan: 0365 m_qm->addNumberFilter( m_filter.field(), m_filter.numValue, 0366 (Collections::QueryMaker::NumberComparison)m_filter.condition ); 0367 break; 0368 case MetaQueryWidget::Between: 0369 m_qm->beginAnd(); 0370 m_qm->addNumberFilter( m_filter.field(), qMin(m_filter.numValue, m_filter.numValue2)-1, 0371 Collections::QueryMaker::GreaterThan ); 0372 m_qm->addNumberFilter( m_filter.field(), qMax(m_filter.numValue, m_filter.numValue2)+1, 0373 Collections::QueryMaker::LessThan ); 0374 m_qm->endAndOr(); 0375 break; 0376 default: 0377 ; 0378 } 0379 } 0380 else 0381 { 0382 switch( m_filter.condition ) 0383 { 0384 case MetaQueryWidget::Equals: 0385 m_qm->addFilter( m_filter.field(), m_filter.value, true, true ); 0386 break; 0387 case MetaQueryWidget::Contains: 0388 if( m_filter.field() == 0 ) 0389 { 0390 // simple search 0391 // TODO: split different words and make separate searches 0392 m_qm->beginOr(); 0393 m_qm->addFilter( Meta::valArtist, m_filter.value ); 0394 m_qm->addFilter( Meta::valTitle, m_filter.value ); 0395 m_qm->addFilter( Meta::valAlbum, m_filter.value ); 0396 m_qm->addFilter( Meta::valGenre, m_filter.value ); 0397 m_qm->addFilter( Meta::valUrl, m_filter.value ); 0398 m_qm->addFilter( Meta::valComment, m_filter.value ); 0399 m_qm->addFilter( Meta::valLabel, m_filter.value ); 0400 m_qm->endAndOr(); 0401 } 0402 else 0403 { 0404 m_qm->addFilter( m_filter.field(), m_filter.value ); 0405 } 0406 break; 0407 default: 0408 ; 0409 } 0410 } 0411 0412 m_qm->setQueryType( Collections::QueryMaker::Custom ); 0413 m_qm->addReturnValue( Meta::valUniqueId ); 0414 0415 connect( m_qm.data(), &Collections::QueryMaker::newResultReady, 0416 this, &TagMatchBias::updateReady, Qt::QueuedConnection ); 0417 connect( m_qm.data(), &Collections::QueryMaker::queryDone, 0418 this, &TagMatchBias::updateFinished, Qt::QueuedConnection ); 0419 m_qm.data()->run(); 0420 } 0421 0422 QString 0423 Dynamic::TagMatchBias::nameForCondition( MetaQueryWidget::FilterCondition cond ) 0424 { 0425 switch( cond ) 0426 { 0427 case MetaQueryWidget::Equals: return QStringLiteral("equals"); 0428 case MetaQueryWidget::GreaterThan: return QStringLiteral("greater"); 0429 case MetaQueryWidget::LessThan: return QStringLiteral("less"); 0430 case MetaQueryWidget::Between: return QStringLiteral("between"); 0431 case MetaQueryWidget::OlderThan: return QStringLiteral("older"); 0432 case MetaQueryWidget::Contains: return QStringLiteral("contains"); 0433 default: 0434 ;// the other conditions are only for the advanced playlist generator 0435 } 0436 return QString(); 0437 } 0438 0439 MetaQueryWidget::FilterCondition 0440 Dynamic::TagMatchBias::conditionForName( const QString &name ) 0441 { 0442 if( name == QLatin1String("equals") ) return MetaQueryWidget::Equals; 0443 else if( name == QLatin1String("greater") ) return MetaQueryWidget::GreaterThan; 0444 else if( name == QLatin1String("less") ) return MetaQueryWidget::LessThan; 0445 else if( name == QLatin1String("between") ) return MetaQueryWidget::Between; 0446 else if( name == QLatin1String("older") ) return MetaQueryWidget::OlderThan; 0447 else if( name == QLatin1String("contains") ) return MetaQueryWidget::Contains; 0448 else return MetaQueryWidget::Equals; 0449 } 0450 0451 bool 0452 Dynamic::TagMatchBias::matches( const Meta::TrackPtr &track ) const 0453 { 0454 QVariant value = Meta::valueForField( m_filter.field(), track ); 0455 0456 bool result = false; 0457 if( m_filter.isDate() ) 0458 { 0459 switch( m_filter.condition ) 0460 { 0461 case MetaQueryWidget::LessThan: 0462 result = value.toLongLong() < m_filter.numValue; 0463 break; 0464 case MetaQueryWidget::Equals: 0465 result = value.toLongLong() == m_filter.numValue; 0466 break; 0467 case MetaQueryWidget::GreaterThan: 0468 result = value.toLongLong() > m_filter.numValue; 0469 break; 0470 case MetaQueryWidget::Between: 0471 result = value.toLongLong() > m_filter.numValue && 0472 value.toLongLong() < m_filter.numValue2; 0473 break; 0474 case MetaQueryWidget::OlderThan: 0475 result = value.toLongLong() < m_filter.numValue + QDateTime::currentDateTimeUtc().toSecsSinceEpoch(); 0476 break; 0477 default: 0478 ; 0479 } 0480 } 0481 else if( m_filter.isNumeric() ) 0482 { 0483 switch( m_filter.condition ) 0484 { 0485 case MetaQueryWidget::LessThan: 0486 result = value.toLongLong() < m_filter.numValue; 0487 break; 0488 case MetaQueryWidget::Equals: 0489 result = value.toLongLong() == m_filter.numValue; 0490 break; 0491 case MetaQueryWidget::GreaterThan: 0492 result = value.toLongLong() > m_filter.numValue; 0493 break; 0494 case MetaQueryWidget::Between: 0495 result = value.toLongLong() > m_filter.numValue && 0496 value.toLongLong() < m_filter.numValue2; 0497 break; 0498 default: 0499 ; 0500 } 0501 } 0502 else 0503 { 0504 switch( m_filter.condition ) 0505 { 0506 case MetaQueryWidget::Equals: 0507 result = value.toString() == m_filter.value; 0508 break; 0509 case MetaQueryWidget::Contains: 0510 result = value.toString().contains( m_filter.value, Qt::CaseInsensitive ); 0511 break; 0512 default: 0513 ; 0514 } 0515 } 0516 if( m_invert ) 0517 return !result; 0518 else 0519 return result; 0520 } 0521 0522