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