File indexing completed on 2025-01-19 04:24:08

0001 /****************************************************************************************
0002  * Copyright (c) 2007 Leo Franchi <lfranchi@gmail.com>                                  *
0003  * Copyright (c) 2009 simon.esneault <simon.esneault@gmail.com>                         *
0004  * Copyright (c) 2010 Daniel Faust <hessijames@gmail.com>                               *
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) any later           *
0009  * version.                                                                             *
0010  *                                                                                      *
0011  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0012  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0013  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0014  *                                                                                      *
0015  * You should have received a copy of the GNU General Public License along with         *
0016  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0017  ****************************************************************************************/
0018 
0019 #define DEBUG_PREFIX "LabelsApplet"
0020 
0021 #include "LabelsApplet.h"
0022 
0023 #include "App.h"
0024 #include "EngineController.h"
0025 #include "PaletteHandler.h"
0026 #include "amarokurls/AmarokUrl.h"
0027 #include "context/Theme.h"
0028 #include "context/applets/labels/LabelGraphicsItem.h"
0029 #include "context/widgets/AppletHeader.h"
0030 #include "core/meta/Meta.h"
0031 #include "core/support/Debug.h"
0032 
0033 #include <Plasma/IconWidget>
0034 #include <Plasma/Containment>
0035 
0036 #include <KConfigDialog>
0037 #include <KGlobalSettings>
0038 #include <KComboBox>
0039 
0040 #include <QGraphicsLinearLayout>
0041 #include <QGraphicsProxyWidget>
0042 #include <QPropertyAnimation>
0043 #include <KConfigGroup>
0044 #include <QDialogButtonBox>
0045 #include <QPushButton>
0046 #include <QVBoxLayout>
0047 
0048 #define LabelsAppletMaxLabelLength 40 // if a downloaded label is longer than this, don't show it
0049 
0050 LabelsApplet::LabelsApplet( QObject *parent, const QVariantList &args )
0051     : Context::Applet( parent, args ),
0052     m_lastLabelSize( QSizeF(0,0) ),
0053     m_lastLabelBottomAdded( false )
0054 {
0055     setHasConfigurationInterface( true );
0056 }
0057 
0058 LabelsApplet::~LabelsApplet()
0059 {
0060     DEBUG_BLOCK
0061     qDeleteAll( m_labelItems );
0062     m_labelItems.clear();
0063     qDeleteAll( m_labelAnimations );
0064     m_labelAnimations.clear();
0065     qDeleteAll( m_labelItemsToDelete );
0066     m_labelItemsToDelete.clear();
0067     qDeleteAll( m_labelAnimationsToDelete );
0068     m_labelAnimationsToDelete.clear();
0069 
0070     if( m_reloadIcon )
0071         delete m_reloadIcon.data();
0072     if( m_settingsIcon )
0073         delete m_settingsIcon.data();
0074 }
0075 
0076 void
0077 LabelsApplet::init()
0078 {
0079     DEBUG_BLOCK
0080 
0081     // Call the base implementation.
0082     Context::Applet::init();
0083 
0084     setBackgroundHints( Plasma::Applet::NoBackground );
0085 
0086     // properly set the size, asking for the whole cv size.
0087     resize( 500, -1 );
0088 
0089     // this applet has to be on top of the applet below, otherwise the completion list of the combobox will shine through the other applet
0090     setZValue( zValue() + 100 );
0091 
0092     // Create the title label
0093     enableHeader( true );
0094     setHeaderText( i18n( "Labels" ) );
0095 
0096     setCollapseHeight( m_header->height() );
0097     setMinimumHeight( collapseHeight() );
0098 
0099     // reload icon
0100     QAction *reloadAction = new QAction( this );
0101     reloadAction->setIcon( QIcon::fromTheme( "view-refresh" ) );
0102     reloadAction->setVisible( true );
0103     reloadAction->setEnabled( true );
0104     reloadAction->setText( i18n( "Reload" ) );
0105     m_reloadIcon = addLeftHeaderAction( reloadAction );
0106     m_reloadIcon.data()->setEnabled( false );
0107     connect( m_reloadIcon.data(), SIGNAL(clicked()), this, SLOT(reload()) );
0108 
0109     // settings icon
0110     QAction *settingsAction = new QAction( this );
0111     settingsAction->setIcon( QIcon::fromTheme( "preferences-system" ) );
0112     settingsAction->setVisible( true );
0113     settingsAction->setEnabled( true );
0114     settingsAction->setText( i18n( "Settings" ) );
0115     m_settingsIcon = addRightHeaderAction( settingsAction );
0116     connect( m_settingsIcon.data(), SIGNAL(clicked()), this, SLOT(showConfigurationInterface()) );
0117 
0118     QGraphicsLinearLayout *layout = new QGraphicsLinearLayout( Qt::Vertical, this );
0119     layout->addItem( m_header );
0120 
0121     m_addLabelProxy = new QGraphicsProxyWidget( this );
0122     m_addLabelProxy.data()->setAttribute( Qt::WA_NoSystemBackground );
0123     m_addLabel = new KComboBox( true );
0124     m_addLabel.data()->setAttribute( Qt::WA_NoSystemBackground );
0125     m_addLabel.data()->setAutoFillBackground( false );
0126     QPalette p = m_addLabel.data()->palette();
0127     QColor c = p.color( QPalette::Base );
0128     c.setAlphaF( 0.4 );
0129     p.setColor( QPalette::Base, c );
0130     m_addLabel.data()->setPalette( p );
0131     m_addLabel.data()->completionObject()->setIgnoreCase( true );
0132     m_addLabel.data()->setCompletionMode( KCompletion::CompletionPopup );
0133     connect( m_addLabel.data(), SIGNAL(returnPressed()), this, SLOT(addLabelPressed()) );
0134     m_addLabelProxy.data()->setWidget( m_addLabel.data() );
0135 
0136     // Read config
0137     KConfigGroup config = Amarok::config("Labels Applet");
0138     m_minCount = config.readEntry( "MinCount", 30 );
0139     m_numLabels = config.readEntry( "NumLabels", 10 );
0140     m_personalCount = config.readEntry( "PersonalCount", 70 );
0141     m_autoAdd = config.readEntry( "AutoAdd", false );
0142     m_minAutoAddCount = config.readEntry( "MinAutoAddCount", 60 );
0143     m_selectedColor = config.readEntry( "SelectedColor", PaletteHandler::highlightColor( 2.0, 0.7 ) );
0144     const QPalette pal;
0145     m_backgroundColor = config.readEntry( "BackgroundColor", pal.color( QPalette::Base ) );
0146 
0147     m_matchArtist = config.readEntry( "MatchArtist", true );
0148     m_matchTitle = config.readEntry( "MatchTitle", true );
0149     m_matchAlbum = config.readEntry( "MatchAlbum", true );
0150     m_blacklist = config.readEntry( "Blacklist", QStringList() );
0151 
0152     const QStringList replacementList = config.readEntry( "ReplacementList", QStringList() );
0153     foreach( const QString &replacement, replacementList )
0154     {
0155         const QStringList parts = replacement.split( '|' );
0156         QString label = parts.at(0);
0157         label = label.replace( "%s", "|" );
0158         label = label.replace( "%p", "%" );
0159         QString replacementValue = parts.at(1);
0160         replacementValue = replacementValue.replace( "%s", "|" );
0161         replacementValue = replacementValue.replace( "%p", "%" );
0162         m_replacementMap.insert( label, replacementValue );
0163     }
0164 
0165     m_stoppedstate = false; // force an update
0166     setStoppedState( true );
0167 
0168     connectSource( "labels" );
0169     connect( dataEngine( "amarok-labels" ), SIGNAL(sourceAdded(QString)),
0170              this, SLOT(connectSource(QString)) );
0171 }
0172 
0173 void
0174 LabelsApplet::setStoppedState( bool stopped )
0175 {
0176     if( stopped == m_stoppedstate )
0177         return;
0178 
0179     m_stoppedstate = stopped;
0180 
0181     m_userLabels.clear();
0182     m_webLabels.clear();
0183 
0184     if( !stopped )
0185     {
0186         m_reloadIcon.data()->setEnabled( true );
0187         m_titleText = i18n( "Labels" );
0188         m_addLabelProxy.data()->show();
0189         m_addLabel.data()->show();
0190         m_addLabel.data()->clearEditText();
0191         // not needed at the moment, since setStoppedState(false) is only called in dataUpdated() and we know that the engine never sends state=started without the user labels;
0192         // so the minimum size is set in constraintsEvent()
0193 //         setCollapseOffHeight( m_header->height() + m_addLabelProxy.data()->size().height() + 2 * standardPadding() );
0194 //         setCollapseOff();
0195     }
0196     else
0197     {
0198         m_reloadIcon.data()->setEnabled( false );
0199         m_titleText = i18n( "Labels: No track playing" );
0200         m_addLabelProxy.data()->hide();
0201         m_addLabel.data()->hide();
0202         setBusy( false );
0203         qDeleteAll( m_labelItems );
0204         m_labelItems.clear();
0205         qDeleteAll( m_labelAnimations );
0206         m_labelAnimations.clear();
0207         setMinimumHeight( collapseHeight() );
0208         setCollapseOn();
0209     }
0210 }
0211 
0212 void
0213 LabelsApplet::reload()
0214 {
0215     DEBUG_BLOCK
0216     if( !m_stoppedstate )
0217         dataEngine( "amarok-labels" )->query( QString( "reload" ) );
0218 }
0219 
0220 void
0221 LabelsApplet::animationFinished()
0222 {
0223     if( QObject::sender() == 0 )
0224         return;
0225 
0226     for( int i=0; i<m_labelAnimations.count(); i++ )
0227     {
0228         if( QObject::sender() == m_labelAnimations.at(i) )
0229         {
0230             if( m_labelItems.at(i) )
0231                 m_labelItems.at(i)->updateHoverStatus();
0232             m_labelAnimations.at(i)->setEasingCurve( QEasingCurve::InOutQuad );
0233             return;
0234         }
0235     }
0236 
0237     prepareGeometryChange();
0238     for( int i=0; i<m_labelAnimationsToDelete.count(); i++ )
0239     {
0240         if( QObject::sender() == m_labelAnimationsToDelete.at(i) )
0241         {
0242             delete m_labelItemsToDelete.at(i);
0243             delete m_labelAnimationsToDelete.at(i);
0244             m_labelItemsToDelete.removeAt(i);
0245             m_labelAnimationsToDelete.removeAt(i);
0246             return;
0247         }
0248     }
0249 }
0250 
0251 void
0252 LabelsApplet::updateLabels()
0253 {
0254     QMap < QString, int > tempLabelsMap;
0255     QMap < QString, int > finalLabelsMap;
0256     // holds all counts of web labels that are added to the final list
0257     QList < int > webCounts;
0258 
0259     // add the user assigned labels directly to the final map
0260     for( int i = 0; i < m_userLabels.count(); i++ )
0261     {
0262         finalLabelsMap.insert( m_userLabels.at( i ), m_personalCount );
0263     }
0264     // add the downloaded labels to the temp map first (if they aren't already in the final map / update value in final map if necessary)
0265     QMapIterator < QString, QVariant > it_infos ( m_webLabels );
0266     while( it_infos.hasNext() )
0267     {
0268         it_infos.next();
0269         if( !finalLabelsMap.contains( it_infos.key() )
0270             && it_infos.value().toInt() >= m_minCount
0271             && QString(it_infos.key()).length() <= LabelsAppletMaxLabelLength
0272             && !m_blacklist.contains( it_infos.key() )
0273             && !( m_matchArtist && QString(it_infos.key()).toLower() == m_artist.toLower() )
0274             && !( m_matchTitle && QString(it_infos.key()).toLower() == m_title.toLower() )
0275             && !( m_matchAlbum && QString(it_infos.key()).toLower() == m_album.toLower() ) )
0276         {
0277             tempLabelsMap.insert( it_infos.key(), it_infos.value().toInt() );
0278         }
0279         else if( finalLabelsMap.contains( it_infos.key() )
0280             && it_infos.value().toInt() > finalLabelsMap[ it_infos.key() ] )
0281         {
0282             finalLabelsMap[ it_infos.key() ] = it_infos.value().toInt();
0283             webCounts += it_infos.value().toInt();
0284         }
0285     }
0286     // then sort the values of the temp map
0287     QList < int > tempLabelsValues = tempLabelsMap.values();
0288     qSort( tempLabelsValues.begin(), tempLabelsValues.end(), qGreater < int > () );
0289     // and copy the highest rated labels to the final map until max. number is reached
0290     // if there are multiple items with equal low rating, move them to minList, sort it and add to the final map until max. number is reached
0291     const int additionalNum = m_numLabels - finalLabelsMap.count();
0292     if( additionalNum > 0 && tempLabelsValues.count() > 0 )
0293     {
0294         int minCount;
0295         QStringList minList;
0296         if( additionalNum <= tempLabelsValues.count() )
0297             minCount = tempLabelsValues.at( additionalNum - 1 );
0298         else
0299             minCount = tempLabelsValues.last();
0300         QMapIterator < QString, int > it_temp ( tempLabelsMap );
0301         while( it_temp.hasNext() )
0302         {
0303             it_temp.next();
0304             if( it_temp.value() > minCount )
0305             {
0306                 finalLabelsMap.insert( it_temp.key(), it_temp.value() );
0307                 webCounts += it_temp.value();
0308             }
0309             else if( it_temp.value() == minCount )
0310             {
0311                 minList += it_temp.key();
0312             }
0313         }
0314         minList.sort();
0315         while( minList.count() > 0 && finalLabelsMap.count() < m_numLabels )
0316         {
0317             finalLabelsMap.insert( minList.first(), minCount );
0318             webCounts += minCount;
0319             minList.takeFirst();
0320         }
0321     }
0322     // now make the label cloud nicer by determinating the quality of the web labels
0323     // a lot of different values (73,68,51) is good, equal values (66,66,33) look suspicious
0324     // 0.7 / 0.3 is a pretty moderate choice; 0.5 / 0.5 would be more extreme
0325     const float qualityFactor = ( webCounts.count() > 0 ) ? 0.7 + 0.3 * (new QSet<int>(webCounts.begin(). webCounts.end())).count()/webCounts.count() : 1.0;
0326     // delete all unneeded label items
0327     for( int i=0; i<m_labelItems.count(); i++ )
0328     {
0329         if( !finalLabelsMap.contains( m_labelItems.at(i)->text() ) )
0330         {
0331             m_labelAnimations.at(i)->setEndValue( QPointF( size().width(), m_labelItems.at(i)->pos().y() ) );
0332             m_labelAnimations.at(i)->setEasingCurve( QEasingCurve::InQuad );
0333             m_labelAnimations.at(i)->start();
0334             m_labelItemsToDelete.append( m_labelItems.at(i) );
0335             m_labelAnimationsToDelete.append( m_labelAnimations.at(i) );
0336             m_labelItems.removeAt(i);
0337             m_labelAnimations.removeAt(i);
0338             i--;
0339         }
0340     }
0341 
0342     // and finally create the LabelGraphicsItems
0343     // add them to a temp list first, so they are in the same order as the final label items map (sorted alphabetically)
0344     QList < LabelGraphicsItem * > tempLabelItems;
0345     QList < QPropertyAnimation * > tempLabelAnimations;
0346     QMapIterator < QString, int > it_final ( finalLabelsMap );
0347     while( it_final.hasNext() )
0348     {
0349         it_final.next();
0350 
0351         if( it_final.key().isEmpty() ) // empty labels don't make sense but they cause a freeze
0352             continue;
0353 
0354         // quality of web labels adjusted value
0355         int adjustedCount = qualityFactor * it_final.value();
0356         if( m_userLabels.contains( it_final.key() ) && adjustedCount < m_personalCount )
0357             adjustedCount = m_personalCount;
0358 
0359         const qreal f_size = qMax( adjustedCount / 10.0 - 5.0, -2.0 );
0360 
0361         LabelGraphicsItem *labelGraphics = 0;
0362         QPropertyAnimation *labelAnimation = 0;
0363         for( int i=0; i<m_labelItems.count(); i++ )
0364         {
0365             if( m_labelItems.at(i)->text() == it_final.key() )
0366             {
0367                 labelGraphics = m_labelItems.at(i);
0368                 labelGraphics->setDeltaPointSize( f_size );
0369                 labelGraphics->setSelected( m_userLabels.contains( it_final.key() ) );
0370                 if( !m_lastLabelSize.isEmpty() && m_lastLabelName == m_labelItems.at(i)->text() )
0371                 {
0372                     const qreal x = labelGraphics->pos().x() - ( labelGraphics->boundingRect().width()  - m_lastLabelSize.width()  ) / 2;
0373                     const qreal y = labelGraphics->pos().y() - ( labelGraphics->boundingRect().height() - m_lastLabelSize.height() ) / 2;
0374                     labelGraphics->setPos( x, y );
0375                     m_lastLabelSize = QSizeF( 0, 0 );
0376                     m_lastLabelName.clear();
0377                 }
0378                 labelAnimation = m_labelAnimations.at(i);
0379                 break;
0380             }
0381         }
0382         if( !labelGraphics )
0383         {
0384             labelGraphics = new LabelGraphicsItem( it_final.key(), f_size, this );
0385             labelGraphics->setSelectedColor( m_selectedColor );
0386             labelGraphics->setBackgroundColor( m_backgroundColor );
0387             labelGraphics->showBlacklistButton( !m_allLabels.contains(it_final.key()) );
0388             labelGraphics->setSelected( m_userLabels.contains( it_final.key() ) );
0389             if( m_lastLabelBottomAdded )
0390             {
0391                 labelGraphics->setPos( m_addLabelProxy.data()->pos().x(), m_addLabelProxy.data()->pos().y() + m_addLabelProxy.data()->size().height()/2 - labelGraphics->boundingRect().height()/2 );
0392                 m_lastLabelBottomAdded = false;
0393             }
0394             connect( labelGraphics, SIGNAL(toggled(QString)), SLOT(toggleLabel(QString)) );
0395             connect( labelGraphics, SIGNAL(list(QString)), SLOT(listLabel(QString)) );
0396             connect( labelGraphics, SIGNAL(blacklisted(QString)), SLOT(blacklistLabel(QString)) );
0397 
0398             labelAnimation = new QPropertyAnimation( labelGraphics, "pos" );
0399             labelAnimation->setEasingCurve( QEasingCurve::OutQuad );
0400             connect( labelAnimation, SIGNAL(finished()), this, SLOT(animationFinished()) );
0401         }
0402         tempLabelItems.append( labelGraphics );
0403         tempLabelAnimations.append( labelAnimation );
0404     }
0405     // copy the temp list to the final list
0406     m_labelItems = tempLabelItems;
0407     m_labelAnimations = tempLabelAnimations;
0408 
0409     // should be unnecessary, but better safe than sorry
0410     m_lastLabelName.clear();
0411     m_lastLabelSize = QSizeF( 0, 0 );
0412     m_lastLabelBottomAdded = false;
0413 
0414     constraintsEvent(); // don't use updateConstraints() in order to avoid labels displayed at pos. 0,0 for a moment
0415 }
0416 
0417 void
0418 LabelsApplet::constraintsEvent( Plasma::Constraints constraints )
0419 {
0420     Context::Applet::constraintsEvent( constraints );
0421 
0422     setHeaderText( m_titleText );
0423 
0424     if( !m_stoppedstate )
0425     {
0426         const qreal horzontalPadding = standardPadding() / 2;
0427         const qreal verticalPadding = standardPadding() / 2;
0428         qreal x_pos;
0429         qreal y_pos = m_header->boundingRect().bottom() + 1.5 * standardPadding();
0430         qreal width = 0;
0431         qreal height = 0;
0432         int start_index = 0;
0433         int end_index = -1;
0434         qreal max_width = size().width() - 2 * standardPadding();
0435         for( int i = 0; i < m_labelItems.count(); i++ )
0436         {
0437             QRectF l_size = m_labelItems.at(i)->boundingRect();
0438             if( width + l_size.width() + horzontalPadding <= max_width || i == 0 )
0439             {
0440                 width += l_size.width();
0441                 if( i != 0 )
0442                     width += horzontalPadding;
0443                 if( l_size.height() > height )
0444                     height = l_size.height();
0445                 end_index = i;
0446             }
0447             else
0448             {
0449                 x_pos = ( max_width - width ) / 2 + standardPadding();
0450                 for( int j = start_index; j <= end_index; j++ )
0451                 {
0452                     const QRectF c_size = m_labelItems.at(j)->boundingRect();
0453                     const QPointF pos = QPointF( x_pos, y_pos + (height-c_size.height())/2 );
0454                     if( m_labelItems.at(j)->pos() == QPointF(0,0) )
0455                         m_labelItems.at(j)->setPos( -c_size.width(), pos.y() );
0456                     m_labelAnimations.at(j)->setEndValue( pos );
0457                     if( m_labelAnimations.at(j)->state() != QAbstractAnimation::Running )
0458                         m_labelAnimations.at(j)->start();
0459                     m_labelItems.at(j)->updateHoverStatus();
0460                     x_pos += c_size.width() + horzontalPadding;
0461                 }
0462                 y_pos += height + verticalPadding;
0463                 width = l_size.width();
0464                 height = l_size.height();
0465                 start_index = i;
0466                 end_index = i;
0467             }
0468         }
0469         x_pos = ( max_width - width ) / 2 + standardPadding();
0470         for( int j = start_index; j <= end_index; j++ )
0471         {
0472             const QRectF c_size = m_labelItems.at(j)->boundingRect();
0473             const QPointF pos = QPointF( x_pos, y_pos + (height-c_size.height())/2 );
0474             if( m_labelItems.at(j)->pos() == QPointF(0,0) )
0475                 m_labelItems.at(j)->setPos( -c_size.width(), pos.y() );
0476             m_labelAnimations.at(j)->setEndValue( pos );
0477             if( m_labelAnimations.at(j)->state() != QAbstractAnimation::Running )
0478                 m_labelAnimations.at(j)->start();
0479             m_labelItems.at(j)->updateHoverStatus();
0480             x_pos += c_size.width() + horzontalPadding;
0481         }
0482         if( m_labelItems.count() > 0 )
0483             y_pos += height + standardPadding();
0484 
0485         const qreal addLabelProxyWidth = qMin( size().width() - 2 * standardPadding(), (qreal)300.0 );
0486         m_addLabelProxy.data()->setPos( ( size().width() - addLabelProxyWidth ) / 2, y_pos );
0487         m_addLabelProxy.data()->setMinimumWidth( addLabelProxyWidth );
0488         m_addLabelProxy.data()->setMaximumWidth( addLabelProxyWidth );
0489         y_pos += m_addLabelProxy.data()->size().height() + standardPadding();
0490 
0491         setMinimumHeight( y_pos );
0492 
0493         setCollapseOffHeight( y_pos );
0494         setCollapseOff();
0495     }
0496 }
0497 
0498 void
0499 LabelsApplet::connectSource( const QString &source )
0500 {
0501     if( source == "labels" )
0502         dataEngine( "amarok-labels" )->connectSource( "labels", this );
0503 }
0504 
0505 void
0506 LabelsApplet::dataUpdated( const QString &name, const Plasma::DataEngine::Data &data ) // SLOT
0507 {
0508     Q_UNUSED( name )
0509 
0510     if( data.isEmpty() )
0511         return;
0512 
0513     if( data.contains( "state" ) && data["state"].toString().contains("started") )
0514         setStoppedState( false );
0515     else if( data.contains( "state" ) && data["state"].toString().contains("stopped") )
0516         setStoppedState( true );
0517 
0518     if( data.contains( "message" ) && data["message"].toString().contains("fetching") )
0519     {
0520         m_titleText = i18n( "Labels: Fetching..." );
0521         if ( !data.contains( "user" ) ) // avoid calling update twice
0522         {
0523             constraintsEvent(); // don't use updateConstraints() in order to avoid labels displayed at pos. 0,0 for a moment
0524         }
0525         if( canAnimate() )
0526             setBusy( true );
0527     }
0528     else if( data.contains( "message" ) )
0529     {
0530         m_titleText = i18n( "Labels: %1", data[ "message" ].toString() );
0531         if( !data.contains( "user" ) ) // avoid calling update twice
0532         {
0533             constraintsEvent(); // don't use updateConstraints() in order to avoid labels displayed at pos. 0,0 for a moment
0534         }
0535         setBusy( false );
0536     }
0537 
0538     if( data.contains( "artist" ) )
0539         m_artist = data[ "artist" ].toString();
0540 
0541     if( data.contains( "title" ) )
0542         m_title = data[ "title" ].toString();
0543 
0544     if( data.contains( "album" ) )
0545         m_album = data[ "album" ].toString();
0546 
0547     if( data.contains( "all" ) )
0548     {
0549         m_allLabels = data[ "all" ].toStringList();
0550         m_allLabels.sort();
0551 
0552         const QString saveText = m_addLabel.data()->lineEdit()->text();
0553         m_addLabel.data()->clear();
0554         m_addLabel.data()->insertItems( 0, m_allLabels );
0555         m_addLabel.data()->completionObject()->setItems( m_allLabels );
0556         m_addLabel.data()->lineEdit()->setText( saveText );
0557     }
0558 
0559     if( data.contains( "user" ) )
0560     {
0561 //         debug() << "new user labels:" << data[ "user" ].toStringList().join(", ");
0562         if( !m_stoppedstate ) // otherwise there's been an error
0563         {
0564             m_userLabels = data[ "user" ].toStringList();
0565             m_webLabels.clear(); // we can safely clear the web labels because user labels will never be updated without the web labels
0566 
0567             if( !data.contains( "web" ) ) // avoid calling updateLabels twice
0568                 updateLabels();
0569         }
0570     }
0571 
0572     if( data.contains( "web" ) )
0573     {
0574 //         debug() << "new web labels:" << QStringList(data[ "web" ].toMap().keys()).join(", ");
0575         if( !m_stoppedstate ) // otherwise there's been an error
0576         {
0577             if( !data.contains( "message" ) )
0578                 m_titleText = i18n( "Labels for %1 by %2", m_title, m_artist );
0579 
0580             setBusy( false );
0581 
0582             m_webLabels = data[ "web" ].toMap();
0583 
0584             // rename/merge web labels if they are present in the replacement map
0585             QMap < QString, QVariant >::iterator it = m_webLabels.begin();
0586             while( it != m_webLabels.end() )
0587             {
0588                 if( m_replacementMap.contains(it.key()) )
0589                 {
0590                     const QString replacement = m_replacementMap.value( it.key() );
0591                     if( m_webLabels.contains(replacement) ) // we have to merge
0592                     {
0593                         m_webLabels[replacement] = qMin( 100, it.value().toInt() + m_webLabels.value(replacement).toInt() );
0594                         it = m_webLabels.erase( it );
0595                     }
0596                     else // just replace
0597                     {
0598                         const int count = it.value().toInt();
0599                         it = m_webLabels.erase( it );
0600                         m_webLabels.insert( replacement, count );
0601                     }
0602                 }
0603                 else
0604                 {
0605                     ++it;
0606                 }
0607             }
0608 
0609             // auto add labels if needed
0610             if( m_userLabels.isEmpty() && m_autoAdd )
0611             {
0612                 QMapIterator < QString, QVariant > it ( m_webLabels );
0613                 while( it.hasNext() )
0614                 {
0615                     it.next();
0616                     if( it.value().toInt() >= m_minAutoAddCount
0617                         && QString(it.key()).length() <= LabelsAppletMaxLabelLength
0618                         && !m_blacklist.contains( it.key() )
0619                         && !( m_matchArtist && QString(it.key()).toLower() == m_artist.toLower() )
0620                         && !( m_matchTitle && QString(it.key()).toLower() == m_title.toLower() )
0621                         && !( m_matchAlbum && QString(it.key()).toLower() == m_album.toLower() ) )
0622                         toggleLabel( it.key() );
0623                 }
0624             }
0625 
0626             updateLabels();
0627         }
0628     }
0629 }
0630 
0631 void
0632 LabelsApplet::addLabelPressed()
0633 {
0634     const QString label = m_addLabel.data()->currentText();
0635 
0636     if( label.isEmpty() )
0637         return;
0638 
0639     if( !m_userLabels.contains( label ) )
0640     {
0641         toggleLabel( label );
0642         m_addLabel.data()->clearEditText();
0643     }
0644 }
0645 
0646 void
0647 LabelsApplet::toggleLabel( const QString &label )
0648 {
0649     DEBUG_BLOCK
0650 
0651     if( label.isEmpty() )
0652         return;
0653 
0654     Meta::TrackPtr track = The::engineController()->currentTrack();
0655     if( !track )
0656         return;
0657 
0658     Meta::LabelPtr labelPtr;
0659 
0660     foreach( const Meta::LabelPtr &labelIt, track->labels() )
0661     {
0662         if( label == labelIt->name() )
0663         {
0664             labelPtr = labelIt;
0665             break;
0666         }
0667     }
0668 
0669     for( int i=0; i<m_labelItems.count(); i++ )
0670     {
0671         if( m_labelItems.at(i)->text() == label )
0672         {
0673             m_lastLabelSize = m_labelItems.at(i)->boundingRect().size();
0674             m_lastLabelName = label;
0675             break;
0676         }
0677     }
0678 
0679     if( m_userLabels.contains( label ) )
0680     {
0681         track->removeLabel( labelPtr );
0682         m_userLabels.removeAll( label );
0683         debug() << "removing label: " << label;
0684     }
0685     else
0686     {
0687         track->addLabel( label );
0688         m_userLabels.append( label );
0689         debug() << "adding label: " << label;
0690         m_lastLabelBottomAdded = true;
0691     }
0692 
0693     if( !m_allLabels.contains( label ) )
0694     {
0695         m_allLabels.append( label );
0696         m_allLabels.sort();
0697 
0698         const QString saveText = m_addLabel.data()->lineEdit()->text();
0699         m_addLabel.data()->clear();
0700         m_addLabel.data()->insertItems( 0, m_allLabels );
0701         m_addLabel.data()->completionObject()->setItems( m_allLabels );
0702         m_addLabel.data()->lineEdit()->setText( saveText );
0703     }
0704 
0705     // usually the engine keeps track of label changes of the playing track
0706     // (except if the labels get auto added, this is why we have to keep m_userLabels up to date)
0707     // but it doesn't work always, so we update
0708     updateLabels();
0709 }
0710 
0711 void
0712 LabelsApplet::listLabel( const QString &label )
0713 {
0714     AmarokUrl bookmark( "amarok://navigate/collections?filter=label:" + AmarokUrl::escape( "=" ) + "%22" + AmarokUrl::escape( label ) + "%22" );
0715     bookmark.run();
0716 }
0717 
0718 void
0719 LabelsApplet::blacklistLabel( const QString &label )
0720 {
0721     if( m_userLabels.contains( label ) )
0722         toggleLabel( label );
0723 
0724     m_blacklist << label;
0725     KConfigGroup config = Amarok::config("Labels Applet");
0726     config.writeEntry( "Blacklist", m_blacklist );
0727 
0728     updateLabels();
0729 }
0730 
0731 void
0732 LabelsApplet::createConfigurationInterface( KConfigDialog *parent )
0733 {
0734     DEBUG_BLOCK
0735 
0736     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
0737     QVBoxLayout *mainLayout = new QVBoxLayout;
0738     parent->setLayout(mainLayout);
0739     QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
0740     okButton->setDefault(true);
0741     okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
0742     parent->connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
0743     parent->connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
0744     mainLayout->addWidget(buttonBox);
0745 
0746     KConfigGroup configuration = config();
0747     QWidget *generalSettings = new QWidget;
0748     ui_GeneralSettings.setupUi( generalSettings );
0749     ui_GeneralSettings.resetColorsPushButton->setIcon( QIcon::fromTheme("fill-color") );
0750     QWidget *blacklistSettings = new QWidget;
0751     ui_BlacklistSettings.setupUi( blacklistSettings );
0752     QWidget *replacementSettings = new QWidget;
0753     ui_ReplacementSettings.setupUi( replacementSettings );
0754     ui_ReplacementSettings.addPushButton->setIcon( QIcon::fromTheme("list-add") );
0755     ui_ReplacementSettings.removePushButton->setIcon( QIcon::fromTheme("list-remove") );
0756 
0757     parent->addPage( generalSettings, i18n( "General Settings" ), "preferences-system" );
0758     parent->addPage( blacklistSettings, i18n( "Blacklist Settings" ), "flag-black" );
0759     parent->addPage( replacementSettings, i18n( "Replacement Settings" ), "system-search" );
0760 
0761     ui_GeneralSettings.minCountSpinBox->setValue( m_minCount );
0762     ui_GeneralSettings.numLabelsSpinBox->setValue( m_numLabels );
0763     ui_GeneralSettings.personalCountSpinBox->setValue( m_personalCount );
0764     ui_GeneralSettings.autoAddCheckBox->setChecked( m_autoAdd );
0765     ui_GeneralSettings.minAutoAddCountSpinBox->setValue( m_minAutoAddCount );
0766     ui_GeneralSettings.selectedColorButton->setColor( m_selectedColor );
0767     ui_GeneralSettings.backgroundColorButton->setColor( m_backgroundColor );
0768 
0769     ui_BlacklistSettings.matchArtistCheckBox->setChecked( m_matchArtist );
0770     ui_BlacklistSettings.matchTitleCheckBox->setChecked( m_matchTitle );
0771     ui_BlacklistSettings.matchAlbumCheckBox->setChecked( m_matchAlbum );
0772     ui_BlacklistSettings.blacklistEditListBox->insertStringList( m_blacklist );
0773 
0774     QHashIterator < QString, QString > it ( m_replacementMap );
0775     while( it.hasNext() )
0776     {
0777         it.next();
0778         new QTreeWidgetItem( ui_ReplacementSettings.replacementTreeWidget, QStringList() << it.key() << it.value() );
0779     }
0780 
0781     connect( ui_GeneralSettings.resetColorsPushButton, SIGNAL(clicked()), this, SLOT(settingsResetColors()) );
0782     connect( ui_ReplacementSettings.addPushButton, SIGNAL(clicked()), this, SLOT(settingsAddReplacement()) );
0783     connect( ui_ReplacementSettings.removePushButton, SIGNAL(clicked()), this, SLOT(settingsRemoveReplacement()) );
0784     connect( parent, SIGNAL(accepted()), this, SLOT(saveSettings()) );
0785 }
0786 
0787 void
0788 LabelsApplet::saveSettings()
0789 {
0790     DEBUG_BLOCK
0791     KConfigGroup config = Amarok::config("Labels Applet");
0792 
0793     m_minCount = ui_GeneralSettings.minCountSpinBox->value();
0794     m_numLabels = ui_GeneralSettings.numLabelsSpinBox->value();
0795     m_personalCount = ui_GeneralSettings.personalCountSpinBox->value();
0796     m_autoAdd = ui_GeneralSettings.autoAddCheckBox->checkState() == Qt::Checked;
0797     m_minAutoAddCount = ui_GeneralSettings.minAutoAddCountSpinBox->value();
0798     m_selectedColor = ui_GeneralSettings.selectedColorButton->color();
0799     m_backgroundColor = ui_GeneralSettings.backgroundColorButton->color();
0800 
0801     m_matchArtist = ui_BlacklistSettings.matchArtistCheckBox->checkState() == Qt::Checked;
0802     m_matchTitle = ui_BlacklistSettings.matchTitleCheckBox->checkState() == Qt::Checked;
0803     m_matchAlbum = ui_BlacklistSettings.matchAlbumCheckBox->checkState() == Qt::Checked;
0804     m_blacklist = ui_BlacklistSettings.blacklistEditListBox->items();
0805 
0806     m_replacementMap.clear();
0807     for( int i=0; i<ui_ReplacementSettings.replacementTreeWidget->topLevelItemCount(); i++ )
0808     {
0809         QTreeWidgetItem *item = ui_ReplacementSettings.replacementTreeWidget->topLevelItem( i );
0810         m_replacementMap.insert( item->text(0), item->text(1) );
0811     }
0812 
0813     config.writeEntry( "NumLabels", m_numLabels );
0814     config.writeEntry( "MinCount", m_minCount );
0815     config.writeEntry( "PersonalCount", m_personalCount );
0816     config.writeEntry( "AutoAdd", m_autoAdd );
0817     config.writeEntry( "MinAutoAddCount", m_minAutoAddCount );
0818     config.writeEntry( "SelectedColor", m_selectedColor );
0819     config.writeEntry( "BackgroundColor", m_backgroundColor );
0820 
0821     config.writeEntry( "MatchArtist", m_matchArtist );
0822     config.writeEntry( "MatchTitle", m_matchTitle );
0823     config.writeEntry( "MatchAlbum", m_matchAlbum );
0824     config.writeEntry( "Blacklist", m_blacklist );
0825 
0826     QStringList replacementList;
0827     QHashIterator < QString, QString > it ( m_replacementMap );
0828     while( it.hasNext() )
0829     {
0830         it.next();
0831         QString label = it.key();
0832         label = label.replace( '%', "%p" );
0833         label = label.replace( '|', "%s" );
0834         QString replacement = it.value();
0835         replacement = replacement.replace( '%', "%p" );
0836         replacement = replacement.replace( '|', "%s" );
0837         replacementList.append( label + '|' + replacement );
0838     }
0839     config.writeEntry( "ReplacementList", replacementList );
0840 
0841     for( int i=0; i<m_labelItems.count(); i++ )
0842     {
0843         m_labelItems.at(i)->setSelectedColor( m_selectedColor );
0844         m_labelItems.at(i)->setBackgroundColor( m_backgroundColor );
0845     }
0846 
0847     reload();
0848 }
0849 
0850 void
0851 LabelsApplet::settingsResetColors()
0852 {
0853     ui_GeneralSettings.selectedColorButton->setColor( PaletteHandler::highlightColor( 2.0, 0.7 ) );
0854     const QPalette pal;
0855     ui_GeneralSettings.backgroundColorButton->setColor( pal.color( QPalette::Base ) );
0856 }
0857 
0858 void
0859 LabelsApplet::settingsAddReplacement()
0860 {
0861     const QString label = ui_ReplacementSettings.labelLineEdit->text();
0862     const QString replacement = ui_ReplacementSettings.replacementLineEdit->text();
0863 
0864     if( label.isEmpty() || replacement.isEmpty() )
0865         return;
0866 
0867     new QTreeWidgetItem( ui_ReplacementSettings.replacementTreeWidget, QStringList() << label << replacement );
0868     ui_ReplacementSettings.labelLineEdit->clear();
0869     ui_ReplacementSettings.replacementLineEdit->clear();
0870 }
0871 
0872 void
0873 LabelsApplet::settingsRemoveReplacement()
0874 {
0875     for( int i=0; i<ui_ReplacementSettings.replacementTreeWidget->topLevelItemCount(); i++ )
0876     {
0877         QTreeWidgetItem *item = ui_ReplacementSettings.replacementTreeWidget->topLevelItem( i );
0878         if( item->isSelected() )
0879         {
0880             ui_ReplacementSettings.replacementTreeWidget->takeTopLevelItem( i );
0881             i--;
0882         }
0883     }
0884 }
0885 
0886