File indexing completed on 2024-05-05 04:48:25

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