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

0001 /****************************************************************************************
0002  * Copyright (c) 2007 Dan Meltzer <parallelgrapefruit@gmail.com>                        *
0003  * Copyright (c) 2011 Sven Krohlas <sven@asbest-online.de>                              *
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 #include "SearchWidget.h"
0019 #include "core/support/Debug.h"
0020 #include "dialogs/EditFilterDialog.h"
0021 #include "widgets/BoxWidget.h"
0022 
0023 #include <QAction>
0024 #include <QIcon>
0025 #include <QLineEdit>
0026 #include <QPushButton>
0027 #include <QStandardPaths>
0028 #include <QToolBar>
0029 #include <QVBoxLayout>
0030 
0031 #include <KLocalizedString>
0032 #include <KStandardGuiItem>
0033 
0034 
0035 SearchWidget::SearchWidget( QWidget *parent, bool advanced )
0036     : QWidget( parent )
0037     , m_sw( nullptr )
0038     , m_filterAction( nullptr )
0039     , m_timeout( 500 )
0040     , m_runningSearches( 0 )
0041 {
0042     setContentsMargins( 0, 0, 0, 0 );
0043     BoxWidget *searchBox = new BoxWidget( false );
0044     searchBox->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
0045 
0046     m_sw = new Amarok::ComboBox( searchBox );
0047     m_sw->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
0048     m_sw->setFrame( true );
0049     m_sw->setCompletionMode( KCompletion::CompletionPopup );
0050     m_sw->completionObject()->setIgnoreCase( true );
0051     m_sw->setToolTip( i18n( "Enter space-separated terms to search." ) );
0052     m_sw->addItem( KStandardGuiItem::find().icon(), QString() );
0053     connect( m_sw, QOverload<int>::of(&QComboBox::activated),
0054              this, &SearchWidget::onComboItemActivated );
0055     connect( m_sw, &Amarok::ComboBox::editTextChanged, this, &SearchWidget::resetFilterTimeout );
0056     connect( m_sw, QOverload<const QString&>::of(&KComboBox::returnPressed),
0057              this, &SearchWidget::filterNow ); // filterNow() calls addCompletion()
0058     connect( m_sw, QOverload<const QString&>::of(&KComboBox::returnPressed),
0059             this, &SearchWidget::returnPressed );
0060     connect( m_sw, &Amarok::ComboBox::downPressed, this, &SearchWidget::advanceFocus );
0061 
0062     QVBoxLayout *layout = new QVBoxLayout();
0063     layout->addWidget( searchBox );
0064     layout->setContentsMargins( 0, 0, 0, 0 );
0065     setLayout( layout );
0066     setClickMessage( i18n( "Enter search terms here" ) );
0067 
0068     m_toolBar = new QToolBar( searchBox );
0069     m_toolBar->setFixedHeight( m_sw->sizeHint().height() );
0070 
0071     if( advanced )
0072     {
0073         m_filterAction = new QAction( QIcon::fromTheme( "document-properties" ), i18n( "Edit filter" ), this );
0074         m_filterAction->setObjectName( "filter" );
0075         m_toolBar->addAction( m_filterAction );
0076 
0077         connect( m_filterAction, &QAction::triggered, this, &SearchWidget::slotShowFilterEditor );
0078     }
0079 
0080     m_filterTimer.setSingleShot( true );
0081     connect( &m_filterTimer, &QTimer::timeout, this, &SearchWidget::filterNow );
0082 
0083     m_animationTimer.setInterval( 500 );
0084     connect( &m_animationTimer, &QTimer::timeout, this, &SearchWidget::nextAnimationTick );
0085 }
0086 
0087 void
0088 SearchWidget::resetFilterTimeout()
0089 {
0090     m_filterTimer.start( m_timeout );
0091 }
0092 
0093 void
0094 SearchWidget::filterNow()
0095 {
0096     m_filterTimer.stop();
0097     addCompletion( m_sw->currentText() );
0098     Q_EMIT filterChanged( m_sw->currentText() );
0099 }
0100 
0101 void
0102 SearchWidget::advanceFocus()
0103 {
0104     focusNextChild();
0105 }
0106 
0107 void
0108 SearchWidget::addCompletion( const QString &text )
0109 {
0110     int index = m_sw->findText( text );
0111     if( index == -1 )
0112     {
0113         m_sw->addItem( KStandardGuiItem::find().icon(), text );
0114         m_sw->completionObject()->addItem( text );
0115     }
0116 
0117     index = m_sw->findText( text );
0118     m_sw->setCurrentIndex( index );
0119 }
0120 
0121 void
0122 SearchWidget::onComboItemActivated( int index )
0123 {
0124     // if data of UserRole exists, use that as the actual filter string
0125     const QString userFilter = m_sw->itemData( index ).toString();
0126     if( userFilter.isEmpty() )
0127         m_sw->setEditText( m_sw->itemText(index) );
0128     else
0129         m_sw->setEditText( userFilter );
0130 }
0131 
0132 void
0133 SearchWidget::slotShowFilterEditor()
0134 {
0135     EditFilterDialog *fd = new EditFilterDialog( this, m_sw->currentText() );
0136     fd->setAttribute( Qt::WA_DeleteOnClose );
0137     m_filterAction->setEnabled( false );
0138 
0139     connect( fd, &EditFilterDialog::filterChanged, m_sw, &Amarok::ComboBox::setEditText );
0140     connect( fd, &QDialog::finished, this, &SearchWidget::slotFilterEditorFinished );
0141 
0142     fd->show();
0143 }
0144 
0145 void
0146 SearchWidget::slotFilterEditorFinished( int result )
0147 {
0148     m_filterAction->setEnabled( true );
0149 
0150     if( result && !m_sw->currentText().isEmpty() ) // result == QDialog::Accepted
0151         addCompletion( m_sw->currentText() );
0152 }
0153 
0154 QToolBar *
0155 SearchWidget::toolBar()
0156 {
0157     return m_toolBar;
0158 }
0159 
0160 void
0161 SearchWidget::showAdvancedButton( bool show )
0162 {
0163     if( show )
0164     {
0165         if( m_filterAction != nullptr )
0166         {
0167             m_filterAction = new QAction( QIcon::fromTheme( "document-properties" ), i18n( "Edit filter" ), this );
0168             m_filterAction->setObjectName( "filter" );
0169             m_toolBar->addAction( m_filterAction );
0170             connect( m_filterAction, &QAction::triggered, this, &SearchWidget::slotShowFilterEditor );
0171         }
0172     }
0173     else
0174     {
0175         delete m_filterAction;
0176         m_filterAction = nullptr;
0177     }
0178 }
0179 
0180 void
0181 SearchWidget::setClickMessage( const QString &message )
0182 {
0183     QLineEdit *edit = qobject_cast<QLineEdit*>( m_sw->lineEdit() );
0184     edit->setPlaceholderText( message );
0185 }
0186 
0187 void
0188 SearchWidget::setTimeout( quint16 newTimeout )
0189 {
0190     m_timeout = newTimeout;
0191 }
0192 
0193 // public slots:
0194 
0195 void
0196 SearchWidget::setSearchString( const QString &searchString )
0197 {
0198     if( searchString != currentText() ) {
0199         m_sw->setEditText( searchString );
0200         filterNow();
0201     }
0202 }
0203 
0204 void
0205 SearchWidget::searchStarted()
0206 {
0207     m_runningSearches++;
0208 
0209     // start the animation
0210     if( !m_animationTimer.isActive() )
0211     {
0212         m_sw->setItemIcon( m_sw->currentIndex(), QIcon( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/loading1.png" ) ) );
0213         m_currentFrame = 0;
0214         m_animationTimer.start();
0215     }
0216 
0217     // If another search is running it might still have a part of the animation set as its icon.
0218     // As the currentIndex() has changed we don't know which one. We now have to iterate through
0219     // all of them and set the icon correctly. It's not as bad as it sounds: the number is quite
0220     // limited.
0221 
0222     for( int i = 0; i < m_sw->count(); i++ )
0223     {
0224         if( i != m_sw->currentIndex() ) // not the current one, which should be animated!
0225             m_sw->setItemIcon( i, KStandardGuiItem::find().icon() );
0226     }
0227 }
0228 
0229 void
0230 SearchWidget::searchEnded()
0231 {
0232     if( m_runningSearches > 0 ) // just to be sure...
0233         m_runningSearches--;
0234 
0235     // stop the animation
0236     if( m_runningSearches == 0 )
0237     {
0238         m_animationTimer.stop();
0239         saveLineEditStatus();
0240         m_sw->setItemIcon( m_sw->currentIndex(), KStandardGuiItem::find().icon() );
0241         restoreLineEditStatus();
0242     }
0243 }
0244 
0245 
0246 // private Q_SLOTS:
0247 
0248 void
0249 SearchWidget::nextAnimationTick()
0250 {
0251     saveLineEditStatus();
0252 
0253     // switch frames
0254     if( m_currentFrame == 0 )
0255         m_sw->setItemIcon( m_sw->currentIndex(), QIcon( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/loading2.png" ) ) );
0256     else
0257         m_sw->setItemIcon( m_sw->currentIndex(), QIcon( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/loading1.png" ) ) );
0258 
0259     restoreLineEditStatus();
0260     m_currentFrame = !m_currentFrame;
0261 }
0262 
0263 
0264 // private:
0265 
0266 void
0267 SearchWidget::restoreLineEditStatus()
0268 {
0269     // restore text changes made by the user
0270     m_sw->setEditText( m_text );
0271 
0272     if( m_hasSelectedText )
0273         m_sw->lineEdit()->setSelection( m_selectionStart, m_selectionLength ); // also sets cursor
0274     else
0275         m_sw->lineEdit()->setCursorPosition( m_cursorPosition );
0276 }
0277 
0278 void
0279 SearchWidget::saveLineEditStatus()
0280 {
0281     // save text changes made by the user
0282     m_text = m_sw->lineEdit()->text();
0283     m_cursorPosition = m_sw->cursorPosition();
0284     m_hasSelectedText = m_sw->lineEdit()->hasSelectedText();
0285     m_selectionStart = m_sw->lineEdit()->selectionStart();
0286     m_selectionLength = m_sw->lineEdit()->selectedText().length();
0287 }
0288