File indexing completed on 2024-05-05 04:49:27

0001 /****************************************************************************************
0002  * Copyright (c) 2008 Daniel Caleb Jones <danielcjones@gmail.com>                       *
0003  * Copyright (c) 2009 Mark Kretschmann <kretschmann@kde.org>                            *
0004  * Copyright (c) 2010 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 #include "core-impl/collections/support/CollectionManager.h"
0022 #include "core/collections/MetaQueryMaker.h"
0023 #include "core/collections/QueryMaker.h"
0024 #include "widgets/MetaQueryWidget.h"
0025 #include "widgets/kdatecombo.h"
0026 #include "FileType.h"
0027 
0028 #include <typeinfo>
0029 
0030 #include <QWidget>
0031 #include <QLineEdit>
0032 #include <QGridLayout>
0033 #include <QHBoxLayout>
0034 #include <QVBoxLayout>
0035 #include <QLabel>
0036 #include <QListView>
0037 #include <QTimeEdit>
0038 
0039 #include <QIcon>
0040 #include <KLocalizedString>
0041 #include <KRatingWidget>
0042 
0043 using namespace Amarok;
0044 
0045 static const int maxHours = 24;
0046 
0047 TimeDistanceWidget::TimeDistanceWidget( QWidget *parent )
0048     : QWidget( parent )
0049 {
0050     m_timeEdit = new QSpinBox(this);
0051     m_timeEdit->setMinimum( 0 );
0052     m_timeEdit->setMaximum( 600 );
0053 
0054     m_unitSelection = new QComboBox(this);
0055     connect( m_timeEdit, QOverload<int>::of(&QSpinBox::valueChanged),
0056              this, &TimeDistanceWidget::slotUpdateComboBoxLabels );
0057     for (int i = 0; i < 7; ++i) {
0058         m_unitSelection->addItem( QString() );
0059     }
0060     slotUpdateComboBoxLabels( 0 );
0061 
0062     QHBoxLayout *hLayout = new QHBoxLayout(this);
0063     hLayout->setContentsMargins(0, 0, 0, 0);
0064     hLayout->addWidget( m_timeEdit );
0065     hLayout->addWidget( m_unitSelection );
0066 }
0067 
0068 qint64 TimeDistanceWidget::timeDistance() const
0069 {
0070     qint64 time = m_timeEdit->value();
0071     switch( m_unitSelection->currentIndex() )
0072     {
0073     case 6:
0074         time *= 365*24*60*60; // years
0075         break;
0076     case 5:
0077         time *=  30*24*60*60; // months
0078         break;
0079     case 4:
0080         time *=   7*24*60*60; // weeks
0081         break;
0082     case 3:
0083         time *=     24*60*60; // days
0084         break;
0085     case 2:
0086         time *=        60*60; // hours
0087         break;
0088     case 1:
0089         time *=           60; // minutes
0090         break;
0091     }
0092 
0093     return time;
0094 }
0095 
0096 void TimeDistanceWidget::setTimeDistance( qint64 value )
0097 {
0098     // as we don't store the time unit we try to reconstruct it
0099     int unit = 0;
0100     if( value > 600 || !(value % 60) ) {
0101         unit = 1;
0102         value /= 60;
0103 
0104         if( value > 600 || !(value % 60) ) {
0105             unit = 2;
0106             value /= 60;
0107 
0108             if( value > 72 || !(value % 24) ) {
0109                 unit = 3;
0110                 value /= 24;
0111 
0112                 if( !(value % 365) ) {
0113                     unit = 6;
0114                     value /= 365;
0115                 } else if( !(value % 30) ) {
0116                     unit = 5;
0117                     value /= 30;
0118                 } else if( !(value % 7) ) {
0119                     unit = 4;
0120                     value /= 7;
0121                 }
0122             }
0123         }
0124     }
0125 
0126     m_unitSelection->setCurrentIndex( unit );
0127     m_timeEdit->setValue( value );
0128 }
0129 
0130 void TimeDistanceWidget::slotUpdateComboBoxLabels( int value )
0131 {
0132     m_unitSelection->setItemText(0, i18np("second", "seconds", value));
0133     m_unitSelection->setItemText(1, i18np("minute", "minutes", value));
0134     m_unitSelection->setItemText(2, i18np("hour", "hours", value));
0135     m_unitSelection->setItemText(3, i18np("day", "days", value));
0136     m_unitSelection->setItemText(4, i18np("week", "weeks", value));
0137     m_unitSelection->setItemText(5, i18np("month", "months", value));
0138     m_unitSelection->setItemText(6, i18np("year", "years", value));
0139 }
0140 
0141 void
0142 MetaQueryWidget::Filter::setField( qint64 newField )
0143 {
0144     if( m_field == newField )
0145         return;
0146 
0147     // -- reset the value and the condition if the new filter has another type
0148     if( MetaQueryWidget::isNumeric( m_field ) != MetaQueryWidget::isNumeric( newField ) )
0149     {
0150         value.clear();
0151         if( MetaQueryWidget::isNumeric( newField ) )
0152             condition = Equals;
0153         else
0154             condition = Contains;
0155     }
0156     if( !MetaQueryWidget::isDate( m_field ) && MetaQueryWidget::isDate( newField ) )
0157     {
0158         numValue = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
0159         numValue2 = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
0160     }
0161     else
0162     {
0163         numValue = 0;
0164         numValue2 = 0;
0165     }
0166 
0167     if (numValue < minimumValue( newField ) || numValue > maximumValue( newField ) )
0168         numValue = defaultValue( newField );
0169 
0170     if (numValue2 < minimumValue( newField ) || numValue2 > maximumValue( newField ) )
0171         numValue2 = defaultValue( newField );
0172 
0173     m_field = newField;
0174 }
0175 
0176 qint64
0177 MetaQueryWidget::Filter::minimumValue( quint64 field )
0178 {
0179     switch( field )
0180     {
0181     case Meta::valYear: return 1900;
0182     case Meta::valTrackNr: return 0;
0183     case Meta::valDiscNr: return 0;
0184     case Meta::valBpm: return 60;
0185     case Meta::valBitrate: return 60;
0186     case Meta::valSamplerate: return 8000;
0187     case Meta::valFilesize: return 0;
0188     case Meta::valScore: return 0;
0189     case Meta::valPlaycount: return 0;
0190     case Meta::valRating: return 0;
0191     case Meta::valLength: return 0;
0192     default: return 0;
0193     }
0194 }
0195 
0196 qint64
0197 MetaQueryWidget::Filter::maximumValue( quint64 field )
0198 {
0199     switch( field )
0200     {
0201     case Meta::valYear: return 2300;
0202     case Meta::valTrackNr: return 100;
0203     case Meta::valDiscNr: return 10;
0204     case Meta::valBpm: return 200;
0205     case Meta::valBitrate: return 2000;
0206     case Meta::valSamplerate: return 48000;
0207     case Meta::valFilesize: return 1000;
0208     case Meta::valScore: return 100;
0209     case Meta::valPlaycount: return 1000;
0210     case Meta::valRating: return 10;
0211     case Meta::valLength: return maxHours * 60 * 60 - 1;
0212     default: return 0;
0213     }
0214 }
0215 
0216 qint64
0217 MetaQueryWidget::Filter::defaultValue( quint64 field )
0218 {
0219     switch( field )
0220     {
0221     case Meta::valYear: return 1976;
0222     case Meta::valTrackNr: return 0;
0223     case Meta::valDiscNr: return 0;
0224     case Meta::valBpm: return 80;
0225     case Meta::valBitrate: return 160;
0226     case Meta::valSamplerate: return 44100;
0227     case Meta::valFilesize: return 10;
0228     case Meta::valScore: return 0;
0229     case Meta::valPlaycount: return 00;
0230     case Meta::valRating: return 0;
0231     case Meta::valLength: return 3 * 60 + 59;
0232     default: return 0;
0233     }
0234 }
0235 
0236 MetaQueryWidget::MetaQueryWidget( QWidget* parent, bool onlyNumeric, bool noCondition )
0237     : QWidget( parent )
0238     , m_onlyNumeric( onlyNumeric )
0239     , m_noCondition( noCondition )
0240     , m_settingFilter( false )
0241     , m_andLabel(nullptr)
0242     , m_compareSelection(nullptr)
0243     , m_valueSelection1(nullptr)
0244     , m_valueSelection2(nullptr)
0245 {
0246     // note: we are using the strange layout structure because the KRatingWidget size depends on the height.
0247     m_layoutMain = new QVBoxLayout( this );
0248     m_layoutMain->setContentsMargins(0, 0, 0, 0);
0249 
0250     makeFieldSelection();
0251     m_layoutMain->addWidget( m_fieldSelection );
0252 
0253     m_layoutValue = new QHBoxLayout();
0254     m_layoutMain->addLayout(m_layoutValue);
0255 
0256     m_layoutValueLabels = new QVBoxLayout();
0257     m_layoutValue->addLayout(m_layoutValueLabels, 0);
0258     m_layoutValueValues = new QVBoxLayout();
0259     m_layoutValue->addLayout(m_layoutValueValues, 1);
0260 
0261     if( m_onlyNumeric )
0262         m_filter.setField( Meta::valYear );
0263     else
0264         m_filter.setField( 0 );
0265 
0266     setFilter(m_filter);
0267 }
0268 
0269 MetaQueryWidget::~MetaQueryWidget()
0270 {
0271 }
0272 
0273 MetaQueryWidget::Filter
0274 MetaQueryWidget::filter() const
0275 {
0276     // special handling for between
0277     if( m_filter.condition == Contains )
0278     {
0279         Filter f = m_filter;
0280         f.numValue  = qMin(m_filter.numValue, m_filter.numValue2) - 1;
0281         f.numValue2 = qMax(m_filter.numValue, m_filter.numValue2) + 1;
0282     }
0283     return m_filter;
0284 }
0285 
0286 void
0287 MetaQueryWidget::setFilter( const MetaQueryWidget::Filter &value )
0288 {
0289     m_settingFilter = true;
0290     m_filter = value;
0291 
0292     int index = m_fieldSelection->findData( int(m_filter.field()) );
0293     m_fieldSelection->setCurrentIndex( index == -1 ? 0 : index );
0294 
0295     if( !m_noCondition )
0296         makeCompareSelection();
0297     makeValueSelection();
0298     setValueSelection();
0299 
0300     m_settingFilter = false;
0301     Q_EMIT changed(m_filter);
0302 }
0303 
0304 static void addIconItem( QComboBox *box, qint64 field )
0305 {
0306     QString icon = Meta::iconForField( field );
0307     QString text = Meta::i18nForField( field );
0308     if( icon.isEmpty() )
0309         box->addItem( text, field );
0310     else
0311         box->addItem( QIcon::fromTheme( icon ), text, field );
0312 }
0313 
0314 void
0315 MetaQueryWidget::makeFieldSelection()
0316 {
0317     m_fieldSelection = new QComboBox( this );
0318     if (!m_onlyNumeric)
0319     {
0320         m_fieldSelection->addItem( i18n( "Simple Search" ), 0 );
0321         addIconItem( m_fieldSelection, Meta::valUrl );
0322         // note: what about directory?
0323         addIconItem( m_fieldSelection, Meta::valTitle );
0324         addIconItem( m_fieldSelection, Meta::valArtist );
0325         addIconItem( m_fieldSelection, Meta::valAlbumArtist );
0326         addIconItem( m_fieldSelection, Meta::valAlbum );
0327         addIconItem( m_fieldSelection, Meta::valGenre );
0328         addIconItem( m_fieldSelection, Meta::valComposer );
0329     }
0330     addIconItem( m_fieldSelection, Meta::valYear );
0331     if (!m_onlyNumeric)
0332         addIconItem( m_fieldSelection, Meta::valComment );
0333     addIconItem( m_fieldSelection, Meta::valTrackNr );
0334     addIconItem( m_fieldSelection, Meta::valDiscNr );
0335     addIconItem( m_fieldSelection, Meta::valBpm );
0336     addIconItem( m_fieldSelection, Meta::valLength );
0337     addIconItem( m_fieldSelection, Meta::valBitrate );
0338     addIconItem( m_fieldSelection, Meta::valSamplerate );
0339     addIconItem( m_fieldSelection, Meta::valFilesize );
0340     if (!m_onlyNumeric)
0341         addIconItem( m_fieldSelection, Meta::valFormat );
0342     addIconItem( m_fieldSelection, Meta::valCreateDate );
0343     addIconItem( m_fieldSelection, Meta::valScore );
0344     addIconItem( m_fieldSelection, Meta::valRating );
0345     addIconItem( m_fieldSelection, Meta::valFirstPlayed );
0346     addIconItem( m_fieldSelection, Meta::valLastPlayed );
0347     addIconItem( m_fieldSelection, Meta::valPlaycount );
0348     if (!m_onlyNumeric)
0349         addIconItem( m_fieldSelection, Meta::valLabel );
0350     addIconItem( m_fieldSelection, Meta::valModified );
0351     connect( m_fieldSelection, QOverload<int>::of(&QComboBox::currentIndexChanged),
0352              this, &MetaQueryWidget::fieldChanged );
0353 }
0354 
0355 void
0356 MetaQueryWidget::fieldChanged( int i )
0357 {
0358     if( m_settingFilter )
0359         return;
0360 
0361     qint64 field = 0;
0362     if( i<0 || i>=m_fieldSelection->count() )
0363         field = m_fieldSelection->itemData( 0 ).toInt();
0364     else
0365         field = m_fieldSelection->itemData( i ).toInt();
0366 
0367     m_filter.setField( field );
0368 
0369     // in the fieldChanged slot we assume that the field was really changed,
0370     // so we don't have a problem with throwing away all the old widgets
0371 
0372     if( !m_noCondition )
0373         makeCompareSelection();
0374     makeValueSelection();
0375 
0376     setValueSelection();
0377 
0378     Q_EMIT changed(m_filter);
0379 }
0380 
0381 void
0382 MetaQueryWidget::compareChanged( int index )
0383 {
0384     FilterCondition condition = FilterCondition( m_compareSelection->itemData( index ).toInt() );
0385 
0386     if( m_filter.condition == condition )
0387         return; // nothing to do
0388 
0389     if( m_filter.isDate() )
0390     {
0391         if(  ( condition == OlderThan || condition == NewerThan )
0392             && m_filter.condition != OlderThan && m_filter.condition != NewerThan
0393           )
0394         {
0395             // fix some inaccuracies caused by the conversion absolute/relative time specifications
0396             // this is actually just for visual consistency
0397             int unit = 0;
0398             qint64 value = QDateTime::currentDateTimeUtc().toSecsSinceEpoch() - m_filter.numValue;
0399             if( value > 600 || !(value % 60) ) {
0400                 unit = 1;
0401                 value /= 60;
0402 
0403                 if( value > 600 || !(value % 60) ) {
0404                     unit = 2;
0405                     value /= 60;
0406 
0407                     if( value > 72 || !(value % 24) ) {
0408                         unit = 3;
0409                         value /= 24;
0410 
0411                         if( !(value % 365) ) {
0412                             unit = 6;
0413                             value /= 365;
0414                         } else if( !(value % 30) ) {
0415                             unit = 5;
0416                             value /= 30;
0417                         } else if( !(value % 7) ) {
0418                             unit = 4;
0419                             value /= 7;
0420                         }
0421                     }
0422                 }
0423             }
0424             switch( unit )
0425             {
0426             case 6:
0427                 value *= 365*24*60*60; // years
0428                 break;
0429             case 5:
0430                 value *=  30*24*60*60; // months
0431                 break;
0432             case 4:
0433                 value *=   7*24*60*60; // weeks
0434                 break;
0435             case 3:
0436                 value *=     24*60*60; // days
0437                 break;
0438             case 2:
0439                 value *=        60*60; // hours
0440                 break;
0441             case 1:
0442                 value *=           60; // minutes
0443                 break;
0444             }
0445             m_filter.numValue = value;
0446         }
0447         else if( condition != OlderThan && condition != NewerThan
0448             && ( m_filter.condition == OlderThan || m_filter.condition == NewerThan )
0449           )
0450         {
0451             m_filter.numValue = QDateTime::currentDateTimeUtc().toSecsSinceEpoch() - m_filter.numValue;
0452         }
0453     }
0454 
0455     m_filter.condition = condition;
0456 
0457     // need to re-generate the value selection fields
0458     makeValueSelection();
0459 
0460     setValueSelection();
0461 
0462     Q_EMIT changed(m_filter);
0463 }
0464 
0465 void
0466 MetaQueryWidget::valueChanged( const QString& value )
0467 {
0468     m_filter.value = value;
0469 
0470     Q_EMIT changed(m_filter);
0471 }
0472 
0473 void
0474 MetaQueryWidget::numValueChanged( int value )
0475 {
0476     m_filter.numValue = value;
0477 
0478     Q_EMIT changed(m_filter);
0479 }
0480 
0481 void
0482 MetaQueryWidget::numValue2Changed( int value )
0483 {
0484     m_filter.numValue2 = value;
0485 
0486     Q_EMIT changed(m_filter);
0487 }
0488 
0489 void
0490 MetaQueryWidget::numValueChanged( qint64 value )
0491 {
0492     m_filter.numValue = value;
0493 
0494     Q_EMIT changed(m_filter);
0495 }
0496 
0497 void
0498 MetaQueryWidget::numValue2Changed( qint64 value )
0499 {
0500     m_filter.numValue2 = value;
0501 
0502     Q_EMIT changed(m_filter);
0503 }
0504 
0505 void
0506 MetaQueryWidget::numValueChanged( const QTime& value )
0507 {
0508     m_filter.numValue = qAbs( value.secsTo( QTime(0,0,0) ) );
0509 
0510     Q_EMIT changed(m_filter);
0511 }
0512 
0513 void
0514 MetaQueryWidget::numValue2Changed( const QTime& value )
0515 {
0516     m_filter.numValue2 = qAbs( value.secsTo( QTime(0,0,0) ) );
0517 
0518     Q_EMIT changed(m_filter);
0519 }
0520 
0521 void
0522 MetaQueryWidget::numValueDateChanged()
0523 {
0524     KDateCombo* dateSelection = qobject_cast<KDateCombo*>( sender() );
0525     if( dateSelection )
0526     {
0527         QDate date;
0528         dateSelection->getDate( &date );
0529         m_filter.numValue = date.startOfDay().toSecsSinceEpoch();
0530 
0531         Q_EMIT changed(m_filter);
0532     }
0533 }
0534 
0535 void
0536 MetaQueryWidget::numValue2DateChanged()
0537 {
0538     KDateCombo* dateSelection = qobject_cast<KDateCombo*>( sender() );
0539     if( dateSelection )
0540     {
0541         QDate date;
0542         dateSelection->getDate( &date );
0543         m_filter.numValue2 = date.startOfDay().toSecsSinceEpoch();
0544 
0545         Q_EMIT changed(m_filter);
0546     }
0547 }
0548 
0549 void
0550 MetaQueryWidget::numValueTimeDistanceChanged()
0551 {
0552     if( !sender() )
0553         return;
0554 
0555     // static_cast. Remember: the TimeDistanceWidget does not have a Q_OBJECT macro
0556     TimeDistanceWidget* distanceSelection = static_cast<TimeDistanceWidget*>( sender()->parent() );
0557     if( distanceSelection )
0558     {
0559         m_filter.numValue = distanceSelection->timeDistance();
0560 
0561         Q_EMIT changed(m_filter);
0562     }
0563 }
0564 
0565 void
0566 MetaQueryWidget::numValueFormatChanged(int index)
0567 {
0568     QComboBox* combo = static_cast<QComboBox*>(sender());
0569     if( combo ) {
0570         m_filter.numValue = combo->itemData( index ).toInt();
0571 
0572         Q_EMIT changed(m_filter);
0573     }
0574 }
0575 
0576 void
0577 MetaQueryWidget::setValueSelection()
0578 {
0579     if( m_compareSelection )
0580         m_layoutValueLabels->addWidget( m_compareSelection );
0581 
0582     if( m_filter.condition == Between )
0583     {
0584         delete m_andLabel; // delete the old label
0585         m_andLabel = new QLabel( i18n( "and" ), this );
0586         m_layoutValueLabels->addWidget( m_andLabel );
0587     }
0588     else
0589     {
0590         delete m_andLabel;
0591         m_andLabel = nullptr;
0592     }
0593 
0594     if( m_valueSelection1 )
0595         m_layoutValueValues->addWidget( m_valueSelection1 );
0596 
0597     if( m_valueSelection2 )
0598         m_layoutValueValues->addWidget( m_valueSelection2 );
0599 }
0600 
0601 
0602 void
0603 MetaQueryWidget::makeCompareSelection()
0604 {
0605     delete m_compareSelection;
0606     m_compareSelection = nullptr;
0607 
0608     qint64 field = m_filter.field();
0609 
0610     if( field == Meta::valFormat )
0611         return; // the field is fixed
0612 
0613     else if( isDate(field) )
0614     {
0615         m_compareSelection = new QComboBox();
0616         m_compareSelection->addItem( conditionToString( Equals, field ), (int)Equals );
0617         m_compareSelection->addItem( conditionToString( LessThan, field ), (int)LessThan );
0618         m_compareSelection->addItem( conditionToString( GreaterThan, field ), (int)GreaterThan );
0619         m_compareSelection->addItem( conditionToString( Between, field ), (int)Between );
0620         m_compareSelection->addItem( conditionToString( OlderThan, field ), (int)OlderThan );
0621         m_compareSelection->addItem( conditionToString( NewerThan, field ), (int)NewerThan );
0622     }
0623     else if( isNumeric(field) )
0624     {
0625         m_compareSelection = new QComboBox();
0626         m_compareSelection->addItem( conditionToString( Equals, field ), (int)Equals );
0627         m_compareSelection->addItem( conditionToString( LessThan, field ), (int)LessThan );
0628         m_compareSelection->addItem( conditionToString( GreaterThan, field ), (int)GreaterThan );
0629         m_compareSelection->addItem( conditionToString( Between, field ), (int)Between );
0630     }
0631     else
0632     {
0633         m_compareSelection = new QComboBox();
0634         m_compareSelection->addItem( conditionToString( Contains, field ), (int)Contains );
0635         m_compareSelection->addItem( conditionToString( Equals, field ), (int)Equals );
0636     }
0637 
0638     // -- select the correct entry (even if the condition is not one of the selection)
0639     int index = m_compareSelection->findData( int(m_filter.condition) );
0640     if( index == -1 )
0641     {
0642         index = 0;
0643         m_filter.condition = FilterCondition(m_compareSelection->itemData( index ).toInt());
0644         compareChanged(index);
0645     }
0646     m_compareSelection->setCurrentIndex( index == -1 ? 0 : index );
0647 
0648     connect( m_compareSelection, QOverload<int>::of(&QComboBox::currentIndexChanged),
0649              this, &MetaQueryWidget::compareChanged );
0650 }
0651 
0652 void
0653 MetaQueryWidget::makeValueSelection()
0654 {
0655     delete m_valueSelection1;
0656     m_valueSelection1 = nullptr;
0657     delete m_valueSelection2;
0658     m_valueSelection2 = nullptr;
0659 
0660     qint64 field = m_filter.field();
0661     if( field == Meta::valUrl )
0662         makeFilenameSelection();
0663     else if( field == Meta::valTitle )
0664         // We,re not going to populate this. There tends to be too many titles.
0665         makeGenericComboSelection( true, nullptr );
0666     else if( field == Meta::valArtist ||
0667         field == Meta::valAlbumArtist ||
0668         field == Meta::valAlbum ||
0669         field == Meta::valGenre ||
0670         field == Meta::valComposer )
0671         makeMetaComboSelection( field );
0672     else if( field == Meta::valYear )
0673         makeGenericNumberSelection( field );
0674     else if( field == Meta::valComment )
0675         makeGenericComboSelection( true, nullptr );
0676     else if( field == Meta::valTrackNr )
0677         makeGenericNumberSelection( field );
0678     else if( field == Meta::valDiscNr )
0679         makeGenericNumberSelection( field );
0680     else if( field == Meta::valBpm )
0681         makeGenericNumberSelection( field );
0682     else if( field == Meta::valLength )
0683         makeLengthSelection();
0684     else if( field == Meta::valBitrate )
0685         makeGenericNumberSelection( field, i18nc("Unit for data rate kilo bit per seconds", "kbps") );
0686     else if( field == Meta::valSamplerate )
0687         makeGenericNumberSelection( field, i18nc("Unit for sample rate", "Hz") );
0688     else if( field == Meta::valFilesize )
0689         makeGenericNumberSelection( field, i18nc("Unit for file size in mega byte", "MiB") );
0690     else if( field == Meta::valFormat )
0691         makeFormatComboSelection();
0692     else if( field == Meta::valCreateDate )
0693         makeDateTimeSelection();
0694     else if( field == Meta::valScore )
0695         makeGenericNumberSelection( field );
0696     else if( field == Meta::valRating )
0697         makeRatingSelection();
0698     else if( field == Meta::valFirstPlayed )
0699         makeDateTimeSelection();
0700     else if( field == Meta::valLastPlayed )
0701         makeDateTimeSelection();
0702     else if( field == Meta::valPlaycount )
0703         makeGenericNumberSelection( field );
0704     else if( field == Meta::valLabel )
0705         makeGenericComboSelection( true, nullptr );
0706     else if( field == Meta::valModified )
0707         makeDateTimeSelection();
0708     else // e.g. the simple search
0709         makeGenericComboSelection( true, nullptr );
0710 }
0711 
0712 void
0713 MetaQueryWidget::makeGenericComboSelection( bool editable, Collections::QueryMaker* populateQuery )
0714 {
0715     KComboBox* combo = new KComboBox( this );
0716     combo->setEditable( editable );
0717 
0718     if( populateQuery != nullptr )
0719     {
0720         m_runningQueries.insert(populateQuery, QPointer<KComboBox>(combo));
0721         connect( populateQuery, &Collections::QueryMaker::newResultReady,
0722                  this, &MetaQueryWidget::populateComboBox );
0723         connect( populateQuery, &Collections::QueryMaker::queryDone,
0724                  this, &MetaQueryWidget::comboBoxPopulated );
0725 
0726         populateQuery->run();
0727     }
0728     combo->setEditText( m_filter.value );
0729 
0730     connect( combo, &KComboBox::editTextChanged,
0731              this, &MetaQueryWidget::valueChanged );
0732 
0733     combo->completionObject()->setIgnoreCase( true );
0734     combo->setCompletionMode( KCompletion::CompletionPopup );
0735     combo->setInsertPolicy( KComboBox::InsertAtTop );
0736     m_valueSelection1 = combo;
0737 }
0738 
0739 void
0740 MetaQueryWidget::makeMetaComboSelection( qint64 field )
0741 {
0742     Collections::QueryMaker* qm = CollectionManager::instance()->queryMaker();
0743     qm->setQueryType( Collections::QueryMaker::Custom );
0744     qm->addReturnValue( field );
0745     qm->setAutoDelete( true );
0746     makeGenericComboSelection( true, qm );
0747 }
0748 
0749 void
0750 MetaQueryWidget::populateComboBox( const QStringList &results )
0751 {
0752     QObject* query = sender();
0753     if( !query )
0754         return;
0755 
0756     QPointer<KComboBox> combo = m_runningQueries.value(query);
0757     if( combo.isNull() )
0758         return;
0759 
0760     // note: adding items seems to reset the edit text, so we have
0761     //   to take care of that.
0762     disconnect( combo.data(), nullptr, this, nullptr );
0763 
0764     // want the results unique and sorted
0765     const QSet<QString> dataSet(results.begin(), results.end());
0766     QStringList dataList = dataSet.values();
0767     dataList.sort();
0768     combo->addItems( dataList );
0769 
0770     KCompletion* comp = combo->completionObject();
0771     comp->setItems( dataList );
0772 
0773     // reset the text and re-enable the signal
0774     combo.data()->setEditText( m_filter.value );
0775     connect( combo.data(), &QComboBox::editTextChanged,
0776              this, &MetaQueryWidget::valueChanged );
0777 }
0778 
0779 void
0780 MetaQueryWidget::makeFormatComboSelection()
0781 {
0782     QComboBox* combo = new QComboBox( this );
0783     combo->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Preferred );
0784     QStringList filetypes = Amarok::FileTypeSupport::possibleFileTypes();
0785     for (int listpos=0;listpos<filetypes.size();listpos++)
0786     {
0787         combo->addItem(filetypes.at(listpos),listpos);
0788     }
0789 
0790     int index = m_fieldSelection->findData( (int)m_filter.numValue );
0791     combo->setCurrentIndex( index == -1 ? 0 : index );
0792 
0793     connect( combo, QOverload<int>::of(&QComboBox::currentIndexChanged),
0794              this, &MetaQueryWidget::numValueFormatChanged );
0795 
0796     m_valueSelection1 = combo;
0797 }
0798 
0799 void
0800 MetaQueryWidget::comboBoxPopulated()
0801 {
0802     QObject* query = sender();
0803     if( !query )
0804         return;
0805 
0806     m_runningQueries.remove( query );
0807 }
0808 
0809 void
0810 MetaQueryWidget::makeFilenameSelection()
0811 {
0812     // Don't populate the combobox. Too many urls.
0813     makeGenericComboSelection( true, nullptr );
0814 }
0815 
0816 
0817 void
0818 MetaQueryWidget::makeRatingSelection()
0819 {
0820     KRatingWidget* ratingWidget = new KRatingWidget();
0821     ratingWidget->setRating( (int)m_filter.numValue );
0822     connect( ratingWidget, QOverload<int>::of(&KRatingWidget::ratingChanged),
0823              this, QOverload<int>::of(&MetaQueryWidget::numValueChanged) );
0824 
0825     m_valueSelection1 = ratingWidget;
0826 
0827     if( m_filter.condition != Between )
0828         return;
0829 
0830     // second KRatingWidget for the between selection
0831     KRatingWidget* ratingWidget2 = new KRatingWidget();
0832     ratingWidget2->setRating( (int)m_filter.numValue2 );
0833     connect( ratingWidget2, QOverload<int>::of(&KRatingWidget::ratingChanged),
0834              this, QOverload<int>::of(&MetaQueryWidget::numValue2Changed) );
0835 
0836     m_valueSelection2 = ratingWidget2;
0837 }
0838 
0839 
0840 void
0841 MetaQueryWidget::makeLengthSelection()
0842 {
0843     QString displayFormat = i18nc( "time format for specifying track length - hours, minutes, seconds", "h:m:ss" );
0844     QTimeEdit* timeSpin = new QTimeEdit();
0845     timeSpin->setDisplayFormat( displayFormat );
0846     timeSpin->setMinimumTime( QTime( 0, 0, 0 ) );
0847     timeSpin->setMaximumTime( QTime( maxHours - 1, 59, 59 ) );
0848     timeSpin->setTime( QTime(0, 0, 0).addSecs( m_filter.numValue ) );
0849 
0850     connect( timeSpin, &QTimeEdit::timeChanged,
0851              this, QOverload<const QTime&>::of(&MetaQueryWidget::numValueChanged) );
0852 
0853     m_valueSelection1 = timeSpin;
0854 
0855     if( m_filter.condition != Between )
0856         return;
0857 
0858     QTimeEdit* timeSpin2 = new QTimeEdit();
0859     timeSpin2->setDisplayFormat( displayFormat );
0860     timeSpin2->setMinimumTime( QTime( 0, 0, 0 ) );
0861     timeSpin2->setMaximumTime( QTime( maxHours - 1, 59, 59 ) );
0862     timeSpin2->setTime( QTime(0, 0, 0).addSecs( m_filter.numValue2 ) );
0863 
0864     connect( timeSpin2, &QTimeEdit::timeChanged,
0865              this, QOverload<const QTime&>::of(&MetaQueryWidget::numValue2Changed) );
0866 
0867     m_valueSelection2 = timeSpin2;
0868 }
0869 
0870 void
0871 MetaQueryWidget::makeGenericNumberSelection( qint64 field, const QString& unit )
0872 {
0873     QSpinBox* spin = new QSpinBox();
0874     spin->setMinimum( Filter::minimumValue( field ) );
0875     spin->setMaximum( Filter::maximumValue( field ) );
0876     if( !unit.isEmpty() )
0877         spin->setSuffix( ' ' + unit );
0878     spin->setValue( m_filter.numValue );
0879 
0880     connect( spin, QOverload<int>::of(&QSpinBox::valueChanged),
0881              this, QOverload<int>::of(&MetaQueryWidget::numValueChanged) );
0882 
0883     m_valueSelection1 = spin;
0884 
0885     if( m_filter.condition != Between )
0886         return;
0887 
0888     // second spin box for the between selection
0889     QSpinBox* spin2 = new QSpinBox();
0890     spin2->setMinimum( Filter::minimumValue( field ) );
0891     spin2->setMaximum( Filter::maximumValue( field ) );
0892     if( !unit.isEmpty() )
0893         spin2->setSuffix( ' ' + unit );
0894     spin2->setValue( m_filter.numValue2 );
0895 
0896     connect( spin2, QOverload<int>::of(&QSpinBox::valueChanged),
0897              this, QOverload<int>::of(&MetaQueryWidget::numValue2Changed) );
0898 
0899     m_valueSelection2 = spin2;
0900 }
0901 
0902 
0903 void
0904 MetaQueryWidget::makeDateTimeSelection()
0905 {
0906     if( m_filter.condition == OlderThan || m_filter.condition == NewerThan )
0907     {
0908         TimeDistanceWidget* distanceSelection = new TimeDistanceWidget();
0909         distanceSelection->setTimeDistance( m_filter.numValue );
0910 
0911         distanceSelection->connectChanged( this, &MetaQueryWidget::numValueTimeDistanceChanged);
0912 
0913         m_valueSelection1 = distanceSelection;
0914     }
0915     else
0916     {
0917         KDateCombo* dateSelection = new KDateCombo();
0918         QDateTime dt;
0919 //         if( m_filter.condition == Contains || m_filter.condition == Equals )
0920 //             dt = QDateTime::currentDateTime();
0921 //         else
0922 //             dt.setSecsSinceEpoch( m_filter.numValue );
0923         dt.setSecsSinceEpoch( m_filter.numValue );
0924         dateSelection->setDate( dt.date() );
0925 
0926         connect( dateSelection, QOverload<int>::of(&KDateCombo::currentIndexChanged),
0927                  this, &MetaQueryWidget::numValueDateChanged );
0928 
0929         m_valueSelection1 = dateSelection;
0930 
0931         if( m_filter.condition != Between )
0932             return;
0933 
0934         // second KDateCombo for the between selection
0935         KDateCombo* dateSelection2 = new KDateCombo();
0936         dt.setSecsSinceEpoch( m_filter.numValue2 );
0937         dateSelection2->setDate( dt.date() );
0938 
0939         connect( dateSelection2, QOverload<int>::of(&KDateCombo::currentIndexChanged),
0940                  this, &MetaQueryWidget::numValue2DateChanged );
0941 
0942         m_valueSelection2 = dateSelection2;
0943     }
0944 }
0945 
0946 
0947 bool
0948 MetaQueryWidget::isNumeric( qint64 field )
0949 {
0950     switch( field )
0951     {
0952     case Meta::valYear:
0953     case Meta::valTrackNr:
0954     case Meta::valDiscNr:
0955     case Meta::valBpm:
0956     case Meta::valLength:
0957     case Meta::valBitrate:
0958     case Meta::valSamplerate:
0959     case Meta::valFilesize:
0960     case Meta::valFormat:
0961     case Meta::valCreateDate:
0962     case Meta::valScore:
0963     case Meta::valRating:
0964     case Meta::valFirstPlayed:
0965     case Meta::valLastPlayed:
0966     case Meta::valPlaycount:
0967     case Meta::valModified:
0968         return true;
0969     default:
0970         return false;
0971     }
0972 }
0973 
0974 bool
0975 MetaQueryWidget::isDate( qint64 field )
0976 {
0977     switch( field )
0978     {
0979     case Meta::valCreateDate:
0980     case Meta::valFirstPlayed:
0981     case Meta::valLastPlayed:
0982     case Meta::valModified:
0983         return true;
0984     default:
0985         return false;
0986     }
0987 }
0988 
0989 QString
0990 MetaQueryWidget::conditionToString( FilterCondition condition, qint64 field )
0991 {
0992     if( isDate(field) )
0993     {
0994         switch( condition )
0995         {
0996         case LessThan:
0997             return i18nc( "The date lies before the given fixed date", "before" );
0998         case Equals:
0999             return i18nc( "The date is the same as the given fixed date", "on" );
1000         case GreaterThan:
1001             return i18nc( "The date is after the given fixed date", "after" );
1002         case Between:
1003             return i18nc( "The date is between the given fixed dates", "between" );
1004         case OlderThan:
1005             return i18nc( "The date lies before the given time interval", "older than" );
1006         case NewerThan:
1007             return i18nc( "The date lies after the given time interval", "newer than" );
1008         default:
1009             ; // fall through
1010         }
1011     }
1012     else if( isNumeric(field) )
1013     {
1014         switch( condition )
1015         {
1016         case LessThan:
1017             return i18n("less than");
1018         case Equals:
1019             return i18nc("a numerical tag (like year or track number) equals a value","equals");
1020         case GreaterThan:
1021             return i18n("greater than");
1022         case Between:
1023             return i18nc( "a numerical tag (like year or track number) is between two values", "between" );
1024         default:
1025             ; // fall through
1026         }
1027     }
1028     else
1029     {
1030         switch( condition )
1031         {
1032         case Equals:
1033             return i18nc("an alphabetical tag (like title or artist name) equals some string","equals");
1034         case Contains:
1035             return i18nc("an alphabetical tag (like title or artist name) contains some string", "contains");
1036         default:
1037             ; // fall through
1038         }
1039     }
1040     return i18n("unknown comparison");
1041 }
1042 
1043 QString
1044 MetaQueryWidget::Filter::fieldToString() const
1045 {
1046     return Meta::shortI18nForField( m_field );
1047 }
1048 
1049 QString MetaQueryWidget::Filter::toString( bool invert ) const
1050 {
1051     // this member is called when there is a keyword that needs numeric attributes
1052     QString strValue1 = value;
1053     QString strValue2 = value;
1054 
1055     if( m_field == Meta::valFormat )
1056     {
1057         strValue1 = Amarok::FileTypeSupport::toString( Amarok::FileType( numValue ));
1058     }
1059     else if( m_field == Meta::valRating )
1060     {
1061         strValue1 = QString::number( (float)numValue / 2 );
1062         strValue2 = QString::number( (float)numValue2 / 2 );
1063     }
1064     else if( isDate() )
1065     {
1066         if( condition == OlderThan || condition == NewerThan )
1067         {
1068             strValue1 = QString::number( numValue  );
1069             strValue2 = QString::number( numValue2 );
1070         }
1071         else
1072         {
1073             strValue1 = QLocale().toString( QDateTime::fromSecsSinceEpoch(numValue).date(), QLocale::ShortFormat );
1074             strValue2 = QLocale().toString( QDateTime::fromSecsSinceEpoch(numValue2).date(), QLocale::ShortFormat );
1075         }
1076     }
1077     else if( isNumeric() )
1078     {
1079         if ( condition != Between )
1080         {
1081             strValue1 = QString::number( numValue );
1082         }
1083         else if (numValue < numValue2) // two values are only used for "between". We want to order them by size
1084         {
1085             strValue1 = QString::number( numValue );
1086             strValue2 = QString::number( numValue2 );
1087         }
1088         else
1089         {
1090             strValue1 = QString::number( numValue2 );
1091             strValue2 = QString::number( numValue );
1092         }
1093     }
1094 
1095     QString result;
1096     if( m_field )
1097         result = fieldToString() + ':';
1098 
1099     switch( condition )
1100     {
1101     case Equals:
1102         {
1103             if( isNumeric() )
1104                 result += strValue1;
1105             else
1106                 result += '=' + QString( "\"%1\"" ).arg( value );
1107             if( invert )
1108                 result.prepend( QChar('-') );
1109             break;
1110         }
1111 
1112     case GreaterThan:
1113         {
1114             result += '>' + strValue1;
1115             if( invert )
1116                 result.prepend( QChar('-') );
1117             break;
1118         }
1119 
1120     case LessThan:
1121         {
1122             result +='<' + strValue1;
1123             if( invert )
1124                 result.prepend( QChar('-') );
1125             break;
1126         }
1127 
1128     case Between:
1129         {
1130             if( invert )
1131                 result = QString( "%1<%2 OR %1>%3" ).arg( result, strValue1, strValue2 );
1132             else
1133                 result = QString( "%1>%2 AND %1<%3" ).arg( result, strValue1, strValue2 );
1134             break;
1135         }
1136 
1137     case OlderThan:
1138     case NewerThan:
1139         {
1140             // a human readable time..
1141             QChar strUnit = 's';
1142             qint64 value = numValue;
1143             if( !(value % 60) ) {
1144                 strUnit = 'M';
1145                 value /= 60;
1146 
1147                 if( !(value % 60) ) {
1148                     strUnit = 'h';
1149                     value /= 60;
1150 
1151                     if( !(value % 24) ) {
1152                         strUnit = 'd';
1153                         value /= 24;
1154 
1155                         if( !(value % 365) ) {
1156                             strUnit = 'y';
1157                             value /= 365;
1158                         } else if( !(value % 30) ) {
1159                             strUnit = 'm';
1160                             value /= 30;
1161                         } else if( !(value % 7) ) {
1162                             strUnit = 'w';
1163                             value /= 7;
1164                         }
1165                     }
1166                 }
1167             }
1168 
1169             if( condition == OlderThan )
1170                 result += '>' + QString::number(value) + strUnit;
1171             else
1172                 result += '<' + QString::number(value) + strUnit;
1173             if( invert )
1174                 result.prepend( QChar('-') );
1175             break;
1176         }
1177 
1178     case Contains:
1179         {
1180             result += QString( "\"%1\"" ).arg( value );
1181             if( invert )
1182                 result.prepend( QChar('-') );
1183             break;
1184         }
1185     }
1186 
1187     return result;
1188 }
1189 
1190 bool
1191 MetaQueryWidget::isFieldSelectorHidden() const
1192 {
1193     return m_fieldSelection->isHidden();
1194 }
1195 
1196 void
1197 MetaQueryWidget::setFieldSelectorHidden( const bool hidden )
1198 {
1199     m_fieldSelection->setVisible( !hidden );
1200 }
1201 
1202 void
1203 MetaQueryWidget::setField( const qint64 field )
1204 {
1205     int index = m_fieldSelection->findData( field );
1206     m_fieldSelection->setCurrentIndex( index == -1 ? 0 : index );
1207 }