Warning, file /multimedia/amarok/src/dialogs/EditFilterDialog.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /**************************************************************************************** 0002 * Copyright (c) 2006 Giovanni Venturi <giovanni@kde-it.org> * 0003 * Copyright (c) 2010 Sergey Ivanov <123kash@gmail.com> * 0004 * * 0005 * This program is free software; you can redistribute it and/or modify it under * 0006 * the terms of the GNU General Public License as published by the Free Software * 0007 * Foundation; either version 2 of the License, or (at your option) any later * 0008 * version. * 0009 * * 0010 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0012 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0013 * * 0014 * You should have received a copy of the GNU General Public License along with * 0015 * this program. If not, see <http://www.gnu.org/licenses/>. * 0016 ****************************************************************************************/ 0017 0018 #define DEBUG_PREFIX "EditFilterDialog" 0019 0020 #include "amarokconfig.h" 0021 #include "ui_EditFilterDialog.h" 0022 #include "core/support/Debug.h" 0023 #include "core-impl/collections/support/CollectionManager.h" 0024 #include "core-impl/collections/support/Expression.h" 0025 #include "dialogs/EditFilterDialog.h" 0026 #include "widgets/TokenDropTarget.h" 0027 0028 #include <QPushButton> 0029 #include <QDialogButtonBox> 0030 0031 #include <KLocalizedString> 0032 #include <KMessageBox> 0033 0034 #define OR_TOKEN Meta::valCustom + 1 0035 #define AND_TOKEN Meta::valCustom + 2 0036 0037 #define AND_TOKEN_CONSTRUCT new Token( i18n( "AND" ), "filename-and-amarok", AND_TOKEN ) 0038 #define OR_TOKEN_CONSTRUCT new Token( i18n( "OR" ), "filename-divider", OR_TOKEN ) 0039 #define SIMPLE_TEXT_CONSTRUCT new Token( i18n( "Simple text" ), "media-track-edit-amarok", 0 ) 0040 0041 EditFilterDialog::EditFilterDialog( QWidget* parent, const QString &text ) 0042 : QDialog( parent ) 0043 , m_ui( new Ui::EditFilterDialog ) 0044 , m_curToken( nullptr ) 0045 , m_separator( " AND " ) 0046 , m_isUpdating() 0047 { 0048 setWindowTitle( i18n( "Edit Filter" ) ); 0049 setLayout( new QVBoxLayout ); 0050 0051 auto mainWidget = new QWidget( this ); 0052 m_ui->setupUi( mainWidget ); 0053 layout()->addWidget( mainWidget ); 0054 0055 auto buttonBox = new QDialogButtonBox( QDialogButtonBox::Reset | QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this ); 0056 connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); 0057 connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 0058 auto resetButton = buttonBox->button( QDialogButtonBox::Reset ); 0059 connect( resetButton, &QPushButton::clicked, this, &EditFilterDialog::slotReset ); 0060 layout()->addWidget( buttonBox ); 0061 0062 m_ui->dropTarget->setRowLimit( 1 ); 0063 0064 initTokenPool(); 0065 0066 m_ui->searchEdit->setText( text ); 0067 updateDropTarget( text ); 0068 updateAttributeEditor(); 0069 0070 connect( m_ui->mqwAttributeEditor, &MetaQueryWidget::changed, 0071 this, &EditFilterDialog::slotAttributeChanged ); 0072 connect( m_ui->cbInvert, &QCheckBox::toggled, 0073 this, &EditFilterDialog::slotInvert ); 0074 connect( m_ui->rbAnd, &QCheckBox::toggled, 0075 this, &EditFilterDialog::slotSeparatorChange ); 0076 connect( m_ui->rbOr, &QCheckBox::toggled, 0077 this, &EditFilterDialog::slotSeparatorChange ); 0078 connect( m_ui->tpTokenPool, &TokenPool::onDoubleClick, 0079 m_ui->dropTarget, &TokenDropTarget::appendToken ); 0080 connect( m_ui->dropTarget, &TokenDropTarget::tokenSelected, 0081 this, &EditFilterDialog::slotTokenSelected ); 0082 connect( m_ui->dropTarget, &TokenDropTarget::changed, 0083 this, &EditFilterDialog::updateSearchEdit ); // in case someone dragged a token around. 0084 0085 connect( m_ui->searchEdit, &QLineEdit::textEdited, 0086 this, &EditFilterDialog::slotSearchEditChanged ); 0087 } 0088 0089 EditFilterDialog::~EditFilterDialog() 0090 { 0091 delete m_ui; 0092 } 0093 0094 void 0095 EditFilterDialog::initTokenPool() 0096 { 0097 0098 m_ui->tpTokenPool->addToken( SIMPLE_TEXT_CONSTRUCT ); 0099 m_ui->tpTokenPool->addToken( tokenForField( Meta::valTitle ) ); 0100 m_ui->tpTokenPool->addToken( tokenForField( Meta::valArtist ) ); 0101 m_ui->tpTokenPool->addToken( tokenForField( Meta::valAlbumArtist ) ); 0102 m_ui->tpTokenPool->addToken( tokenForField( Meta::valAlbum ) ); 0103 m_ui->tpTokenPool->addToken( tokenForField( Meta::valGenre ) ); 0104 m_ui->tpTokenPool->addToken( tokenForField( Meta::valComposer ) ); 0105 m_ui->tpTokenPool->addToken( tokenForField( Meta::valComment ) ); 0106 m_ui->tpTokenPool->addToken( tokenForField( Meta::valUrl ) ); 0107 m_ui->tpTokenPool->addToken( tokenForField( Meta::valYear ) ); 0108 m_ui->tpTokenPool->addToken( tokenForField( Meta::valTrackNr ) ); 0109 m_ui->tpTokenPool->addToken( tokenForField( Meta::valDiscNr ) ); 0110 m_ui->tpTokenPool->addToken( tokenForField( Meta::valBpm ) ); 0111 m_ui->tpTokenPool->addToken( tokenForField( Meta::valLength ) ); 0112 m_ui->tpTokenPool->addToken( tokenForField( Meta::valBitrate ) ); 0113 m_ui->tpTokenPool->addToken( tokenForField( Meta::valSamplerate ) ); 0114 m_ui->tpTokenPool->addToken( tokenForField( Meta::valFilesize ) ); 0115 m_ui->tpTokenPool->addToken( tokenForField( Meta::valFormat ) ); 0116 m_ui->tpTokenPool->addToken( tokenForField( Meta::valCreateDate ) ); 0117 m_ui->tpTokenPool->addToken( tokenForField( Meta::valScore ) ); 0118 m_ui->tpTokenPool->addToken( tokenForField( Meta::valRating ) ); 0119 m_ui->tpTokenPool->addToken( tokenForField( Meta::valFirstPlayed ) ); 0120 m_ui->tpTokenPool->addToken( tokenForField( Meta::valLastPlayed ) ); 0121 m_ui->tpTokenPool->addToken( tokenForField( Meta::valPlaycount ) ); 0122 m_ui->tpTokenPool->addToken( tokenForField( Meta::valLabel ) ); 0123 m_ui->tpTokenPool->addToken( tokenForField( Meta::valModified ) ); 0124 m_ui->tpTokenPool->addToken( OR_TOKEN_CONSTRUCT ); 0125 m_ui->tpTokenPool->addToken( AND_TOKEN_CONSTRUCT ); 0126 } 0127 0128 Token * 0129 EditFilterDialog::tokenForField( const qint64 field ) 0130 { 0131 QString icon = Meta::iconForField( field ); 0132 QString text = Meta::i18nForField( field ); 0133 0134 return new Token( text, icon, field ); 0135 } 0136 0137 EditFilterDialog::Filter & 0138 EditFilterDialog::filterForToken( Token *token ) 0139 { 0140 // a new token! 0141 if( !m_filters.contains( token ) ) { 0142 Filter newFilter; 0143 newFilter.filter.setField( token->value() ); 0144 newFilter.inverted = false; 0145 0146 m_filters.insert( token, newFilter ); 0147 connect( token, &Token::removed, 0148 this, &EditFilterDialog::slotTokenRemoved ); 0149 } 0150 0151 return m_filters[token]; 0152 } 0153 0154 void 0155 EditFilterDialog::slotTokenSelected( Token *token ) 0156 { 0157 DEBUG_BLOCK; 0158 0159 if( m_curToken == token ) 0160 return; // nothing to do 0161 0162 m_curToken = token; 0163 0164 if( m_curToken && m_curToken->value() > Meta::valCustom ) // OR / AND tokens case 0165 m_curToken = nullptr; 0166 0167 updateAttributeEditor(); 0168 } 0169 0170 void 0171 EditFilterDialog::slotTokenRemoved( Token *token ) 0172 { 0173 DEBUG_BLOCK 0174 0175 m_filters.take( token ); 0176 if( m_curToken == token ) 0177 { 0178 m_curToken = nullptr; 0179 updateAttributeEditor(); 0180 } 0181 0182 updateSearchEdit(); 0183 } 0184 0185 0186 void 0187 EditFilterDialog::slotAttributeChanged( const MetaQueryWidget::Filter &newFilter ) 0188 { 0189 DEBUG_BLOCK; 0190 0191 if( m_curToken ) 0192 m_filters[m_curToken].filter = newFilter; 0193 0194 updateSearchEdit(); 0195 } 0196 0197 void 0198 EditFilterDialog::slotInvert( bool checked ) 0199 { 0200 if( m_curToken ) 0201 m_filters[m_curToken].inverted = checked; 0202 0203 updateSearchEdit(); 0204 } 0205 0206 void 0207 EditFilterDialog::slotSeparatorChange() 0208 { 0209 if( m_ui->rbAnd->isChecked() ) 0210 m_separator = QString( " AND " ); 0211 else 0212 m_separator = QString( " OR " ); 0213 0214 updateSearchEdit(); 0215 } 0216 0217 void 0218 EditFilterDialog::slotSearchEditChanged( const QString &filterText ) 0219 { 0220 updateDropTarget( filterText ); 0221 updateAttributeEditor(); 0222 } 0223 0224 void 0225 EditFilterDialog::slotReset() 0226 { 0227 m_ui->dropTarget->clear(); 0228 m_ui->rbAnd->setChecked( true ); 0229 0230 updateAttributeEditor(); 0231 updateSearchEdit(); 0232 } 0233 0234 void 0235 EditFilterDialog::accept() 0236 { 0237 Q_EMIT filterChanged( filter() ); 0238 QDialog::accept(); 0239 } 0240 0241 void 0242 EditFilterDialog::updateAttributeEditor() 0243 { 0244 DEBUG_BLOCK; 0245 0246 if( m_isUpdating ) 0247 return; 0248 m_isUpdating = true; 0249 0250 if( m_curToken ) 0251 { 0252 Filter &filter = filterForToken( m_curToken ); 0253 0254 m_ui->mqwAttributeEditor->setFilter( filter.filter ); 0255 m_ui->cbInvert->setChecked( filter.inverted ); 0256 } 0257 0258 m_ui->mqwAttributeEditor->setEnabled( ( bool )m_curToken ); 0259 m_ui->cbInvert->setEnabled( ( bool )m_curToken ); 0260 0261 m_isUpdating = false; 0262 } 0263 0264 void 0265 EditFilterDialog::updateSearchEdit() 0266 { 0267 DEBUG_BLOCK; 0268 0269 if( m_isUpdating ) 0270 return; 0271 m_isUpdating = true; 0272 0273 m_ui->searchEdit->setText( filter() ); 0274 0275 m_isUpdating = false; 0276 } 0277 0278 void 0279 EditFilterDialog::updateDropTarget( const QString &text ) 0280 { 0281 DEBUG_BLOCK; 0282 0283 if( m_isUpdating ) 0284 return; 0285 m_isUpdating = true; 0286 0287 m_ui->dropTarget->clear(); 0288 0289 // some code duplication, see Collections::semanticDateTimeParser 0290 0291 ParsedExpression parsed = ExpressionParser::parse( text ); 0292 bool AND = false; // need an AND token 0293 bool OR = false; // need an OR token 0294 bool isDateAbsolute = false; 0295 foreach( const or_list &orList, parsed ) 0296 { 0297 foreach( const expression_element &elem, orList ) 0298 { 0299 if( AND ) 0300 m_ui->dropTarget->appendToken( AND_TOKEN_CONSTRUCT ); 0301 else if( OR ) 0302 m_ui->dropTarget->appendToken( OR_TOKEN_CONSTRUCT ); 0303 0304 Filter filter; 0305 filter.filter.setField( !elem.field.isEmpty() ? Meta::fieldForName( elem.field ) : 0 ); 0306 if( filter.filter.field() == Meta::valRating ) 0307 { 0308 filter.filter.numValue = 2 * elem.text.toFloat(); 0309 } 0310 else if( filter.filter.isDate() ) 0311 { 0312 QString strTime = elem.text; 0313 0314 // parse date using local settings 0315 auto date = QLocale().toDate( strTime, QLocale::ShortFormat ); 0316 0317 // parse date using a backup standard independent from local settings 0318 QRegExp shortDateReg("(\\d{1,2})[-.](\\d{1,2})"); 0319 QRegExp longDateReg("(\\d{1,2})[-.](\\d{1,2})[-.](\\d{4})"); 0320 // NOTE for absolute time specifications numValue is a unix timestamp, 0321 // for relative time specifications numValue is a time difference in seconds 'pointing to the past' 0322 if( date.isValid() ) 0323 { 0324 filter.filter.numValue = date.startOfDay().toSecsSinceEpoch(); 0325 isDateAbsolute = true; 0326 } 0327 else if( strTime.contains(shortDateReg) ) 0328 { 0329 filter.filter.numValue = QDate( QDate::currentDate().year(), shortDateReg.cap(2).toInt(), shortDateReg.cap(1).toInt() ).startOfDay().toSecsSinceEpoch(); 0330 isDateAbsolute = true; 0331 } 0332 else if( strTime.contains(longDateReg) ) 0333 { 0334 filter.filter.numValue = QDate( longDateReg.cap(3).toInt(), longDateReg.cap(2).toInt(), longDateReg.cap(1).toInt() ).startOfDay().toSecsSinceEpoch(); 0335 isDateAbsolute = true; 0336 } 0337 else 0338 { 0339 // parse a "#m#d" (discoverability == 0, but without a GUI, how to do it?) 0340 int years = 0, months = 0, days = 0, secs = 0; 0341 QString tmp; 0342 for( int i = 0; i < strTime.length(); i++ ) 0343 { 0344 QChar c = strTime.at( i ); 0345 if( c.isNumber() ) 0346 { 0347 tmp += c; 0348 } 0349 else if( c == 'y' ) 0350 { 0351 years += tmp.toInt(); 0352 tmp.clear(); 0353 } 0354 else if( c == 'm' ) 0355 { 0356 months += tmp.toInt(); 0357 tmp.clear(); 0358 } 0359 else if( c == 'w' ) 0360 { 0361 days += tmp.toInt() * 7; 0362 tmp.clear(); 0363 } 0364 else if( c == 'd' ) 0365 { 0366 days += tmp.toInt(); 0367 tmp.clear(); 0368 } 0369 else if( c == 'h' ) 0370 { 0371 secs += tmp.toInt() * 60 * 60; 0372 tmp.clear(); 0373 } 0374 else if( c == 'M' ) 0375 { 0376 secs += tmp.toInt() * 60; 0377 tmp.clear(); 0378 } 0379 else if( c == 's' ) 0380 { 0381 secs += tmp.toInt(); 0382 tmp.clear(); 0383 } 0384 } 0385 filter.filter.numValue = years*365*24*60*60 + months*30*24*60*60 + days*24*60*60 + secs; 0386 isDateAbsolute = false; 0387 } 0388 } 0389 else if( filter.filter.isNumeric() ) 0390 { 0391 filter.filter.numValue = elem.text.toInt(); 0392 } 0393 0394 if( filter.filter.isDate() ) 0395 { 0396 switch( elem.match ) 0397 { 0398 case expression_element::Less: 0399 if( isDateAbsolute ) 0400 filter.filter.condition = MetaQueryWidget::LessThan; 0401 else 0402 filter.filter.condition = MetaQueryWidget::NewerThan; 0403 break; 0404 case expression_element::More: 0405 if( isDateAbsolute ) 0406 filter.filter.condition = MetaQueryWidget::GreaterThan; 0407 else 0408 filter.filter.condition = MetaQueryWidget::OlderThan; 0409 break; 0410 default: 0411 filter.filter.condition = MetaQueryWidget::Equals; 0412 } 0413 } 0414 else if( filter.filter.isNumeric() ) 0415 { 0416 switch( elem.match ) 0417 { 0418 case expression_element::Equals: 0419 filter.filter.condition = MetaQueryWidget::Equals; 0420 break; 0421 case expression_element::Less: 0422 filter.filter.condition = MetaQueryWidget::LessThan; 0423 break; 0424 case expression_element::More: 0425 filter.filter.condition = MetaQueryWidget::GreaterThan; 0426 break; 0427 case expression_element::Contains: 0428 break; 0429 } 0430 } 0431 else 0432 { 0433 switch( elem.match ) 0434 { 0435 case expression_element::Contains: 0436 filter.filter.condition = MetaQueryWidget::Contains; 0437 break; 0438 case expression_element::Equals: 0439 filter.filter.condition = MetaQueryWidget::Equals; 0440 break; 0441 case expression_element::Less: 0442 case expression_element::More: 0443 break; 0444 } 0445 filter.filter.value = elem.text; 0446 } 0447 0448 filter.inverted = elem.negate; 0449 0450 Token *nToken = filter.filter.field() 0451 ? tokenForField( filter.filter.field() ) 0452 : SIMPLE_TEXT_CONSTRUCT; 0453 m_filters.insert( nToken, filter ); 0454 connect( nToken, &Token::removed, 0455 this, &EditFilterDialog::slotTokenRemoved); 0456 0457 m_ui->dropTarget->appendToken( nToken ); 0458 0459 OR = true; 0460 } 0461 OR = false; 0462 AND = true; 0463 } 0464 0465 m_isUpdating = false; 0466 } 0467 0468 0469 QString 0470 EditFilterDialog::filter() 0471 { 0472 QString filterString; 0473 0474 QList < Token *> tokens = m_ui->dropTarget->tokensAtRow(); 0475 bool join = false; 0476 foreach( Token *token, tokens ) 0477 { 0478 if( token->value() == OR_TOKEN ) 0479 { 0480 filterString.append( " OR " ); 0481 join = false; 0482 } 0483 else if( token->value() == AND_TOKEN ) 0484 { 0485 filterString.append( " AND " ); 0486 join = false; 0487 } 0488 else 0489 { 0490 if( join ) 0491 filterString.append( m_separator ); 0492 Filter &filter = filterForToken( token ); 0493 filterString.append( filter.filter.toString( filter.inverted ) ); 0494 join = true; 0495 } 0496 } 0497 0498 return filterString; 0499 } 0500 0501