Not crap after all...
[amarok.git] / src / tagdialog.cpp
blob4584f9aa1c153f1f8ce5ffc195fe85bd03671a1a
1 // (c) 2004 Mark Kretschmann <markey@web.de>
2 // (c) 2004 Pierpaolo Di Panfilo <pippo_dp@libero.it>
3 // (c) 2005-2006 Alexandre Pereira de Oliveira <aleprj@gmail.com>
4 // See COPYING file for licensing information.
7 // FIXME Config check for this seems to be missing
8 #define HAVE_TUNEPIMP false
10 #include "amarok.h"
11 #include "debug.h"
12 #include "contextbrowser.h"
13 #include "collectionbrowser.h"
14 #include "querybuilder.h"
15 #include "coverfetcher.h"
16 #include "metabundle.h"
17 #include "playlist.h"
18 #include "playlistitem.h"
19 #include "statusbar.h" //for status messages
20 #include "tagdialog.h"
21 #include "tagguesser.h"
22 #include "ui_tagguesserconfigdialog.h"
23 #include "trackpickerdialog.h"
25 #include <tfile.h> //TagLib::File::isWritable
27 #include <kapplication.h>
28 #include <kcombobox.h>
29 #include <kcursor.h>
30 #include <kglobal.h>
31 #include <khtmlview.h>
32 #include <kiconloader.h>
33 #include <klineedit.h>
34 #include <kmessagebox.h>
35 #include <knuminput.h>
36 #include <krun.h>
37 #include <kstandarddirs.h>
38 #include <ktabwidget.h>
39 #include <ktextedit.h>
40 #include <kvbox.h>
42 #include <QCheckBox>
43 #include <qdom.h>
44 #include <QFile>
45 #include <QLabel>
46 #include <QLayout>
47 #include <QPair>
48 #include <QPushButton>
49 #include <QToolTip>
53 class TagDialogWriter : public ThreadManager::Job
55 public:
56 TagDialogWriter( const QMap<QString, MetaBundle> tagsToChange );
57 bool doJob();
58 void completeJob();
59 private:
60 QList<bool> m_failed;
61 QList<MetaBundle> m_tags;
62 bool m_updateView;
63 int m_successCount;
64 int m_failCount;
65 QStringList m_failedURLs;
68 TagDialog::TagDialog( const KUrl& url, QWidget* parent )
69 : TagDialogBase( parent )
70 , m_bundle( url, true )
71 , m_playlistItem( 0 )
72 , m_currentCover( 0 )
74 init();
78 TagDialog::TagDialog( const KUrl::List list, QWidget* parent )
79 : TagDialogBase( parent )
80 , m_bundle()
81 , m_playlistItem( 0 )
82 , m_urlList( list )
83 , m_currentCover( 0 )
85 init();
89 TagDialog::TagDialog( const MetaBundle& mb, PlaylistItem* item, QWidget* parent )
90 : TagDialogBase( parent )
91 , m_bundle( mb )
92 , m_playlistItem( item )
93 , m_currentCover( 0 )
95 init();
99 TagDialog::~TagDialog()
101 DEBUG_BLOCK
103 Amarok::config( "TagDialog" ).writeEntry( "CurrentTab", kTabWidget->currentPageIndex() );
106 void
107 TagDialog::setTab( int id )
109 kTabWidget->setCurrentPage( id );
113 ////////////////////////////////////////////////////////////////////////////////
114 // PRIVATE SLOTS
115 ////////////////////////////////////////////////////////////////////////////////
117 void
118 TagDialog::cancelPressed() //SLOT
120 QApplication::restoreOverrideCursor(); // restore the cursor before closing the dialog
121 reject();
125 void
126 TagDialog::accept() //SLOT
128 pushButton_ok->setEnabled( false ); //visual feedback
129 saveTags();
131 QDialog::accept();
135 inline void
136 TagDialog::openPressed() //SLOT
138 Amarok::invokeBrowser( m_path );
142 inline void
143 TagDialog::previousTrack()
145 if( m_playlistItem )
147 if( !m_playlistItem->itemAbove() ) return;
149 storeTags();
151 m_playlistItem = static_cast<PlaylistItem *>( m_playlistItem->itemAbove() );
153 loadTags( m_playlistItem->url() );
155 else
157 storeTags( *m_currentURL );
159 if( m_currentURL != m_urlList.begin() )
160 --m_currentURL;
161 loadTags( *m_currentURL );
162 enableItems();
164 readTags();
168 inline void
169 TagDialog::nextTrack()
171 if( m_playlistItem )
173 if( !m_playlistItem->itemBelow() ) return;
175 storeTags();
177 m_playlistItem = static_cast<PlaylistItem *>( m_playlistItem->itemBelow() );
179 loadTags( m_playlistItem->url() );
181 else
183 storeTags( *m_currentURL );
185 KUrl::List::iterator next = m_currentURL;
186 ++next;
187 if( next != m_urlList.end() )
188 ++m_currentURL;
189 loadTags( *m_currentURL );
190 enableItems();
192 readTags();
195 inline void
196 TagDialog::perTrack()
198 m_perTrack = !m_perTrack;
199 if( m_perTrack )
201 // just switched to per track mode
202 applyToAllTracks();
203 setSingleTrackMode();
204 loadTags( *m_currentURL );
205 readTags();
207 else
209 storeTags( *m_currentURL );
210 setMultipleTracksMode();
211 readMultipleTracks();
214 enableItems();
218 void
219 TagDialog::enableItems()
221 checkBox_perTrack->setChecked( m_perTrack );
222 pushButton_previous->setEnabled( m_perTrack && m_currentURL != m_urlList.begin() );
223 KUrl::List::ConstIterator next = m_currentURL;
224 ++next;
225 pushButton_next->setEnabled( m_perTrack && next != m_urlList.end());
226 if( m_urlList.count() == 1 )
228 checkBox_perTrack->setEnabled( false );
230 else
232 checkBox_perTrack->setEnabled( true );
237 inline void
238 TagDialog::checkModified() //SLOT
240 pushButton_ok->setEnabled( hasChanged() || storedTags.count() > 0 || storedScores.count() > 0
241 || storedLyrics.count() > 0 || storedRatings.count() > 0 || newLabels.count() > 0 );
244 void
245 TagDialog::loadCover( const QString &artist, const QString &album )
247 if ( m_bundle.artist() != artist || m_bundle.album()!=album )
248 return;
250 // draw the album cover on the dialog
251 QString cover = CollectionDB::instance()->albumImage( m_bundle );
253 if( m_currentCover != cover )
255 pixmap_cover->setPixmap( QPixmap( cover, "PNG" ) );
256 m_currentCover = cover;
258 pixmap_cover->setInformation( m_bundle.artist(), m_bundle.album() );
259 const int s = AmarokConfig::coverPreviewSize();
260 pixmap_cover->setMinimumSize( s, s );
261 pixmap_cover->setMaximumSize( s, s );
265 void
266 TagDialog::setFileNameSchemes() //SLOT
268 KDialog *kDialog = new KDialog(this);
269 Ui::TagGuesserConfigDialog* dialog = new Ui::TagGuesserConfigDialog();
270 dialog->setupUi(kDialog);
271 kDialog->exec();
275 void
276 TagDialog::guessFromFilename() //SLOT
278 TagGuesser guesser( m_bundle.url().path() );
279 if( !guesser.title().isNull() )
280 kLineEdit_title->setText( guesser.title() );
281 if( !guesser.artist().isNull() )
282 kComboBox_artist->setCurrentText( guesser.artist() );
283 if( !guesser.album().isNull() )
284 kComboBox_album->setCurrentText( guesser.album() );
285 if( !guesser.track().isNull() )
286 qSpinBox_track->setValue( guesser.track().toInt() );
287 if( !guesser.comment().isNull() )
288 kTextEdit_comment->setText( guesser.comment() );
289 if( !guesser.year().isNull() )
290 qSpinBox_year->setValue( guesser.year().toInt() );
291 if( !guesser.composer().isNull() )
292 kComboBox_composer->setCurrentText( guesser.composer() );
293 if( !guesser.genre().isNull() )
294 kComboBox_genre->setCurrentText( guesser.genre() );
297 void
298 TagDialog::musicbrainzQuery() //SLOT
300 #if HAVE_TUNEPIMP
301 kDebug() << k_funcinfo << endl;
303 m_mbTrack = m_bundle.url();
304 KTRMLookup* ktrm = new KTRMLookup( m_mbTrack.path(), true );
305 connect( ktrm, SIGNAL( sigResult( KTRMResultList, QString ) ), SLOT( queryDone( KTRMResultList, QString ) ) );
306 connect( pushButton_cancel, SIGNAL( clicked() ), ktrm, SLOT( deleteLater() ) );
308 pushButton_musicbrainz->setEnabled( false );
309 pushButton_musicbrainz->setText( i18n( "Generating audio fingerprint..." ) );
310 QApplication::setOverrideCursor( KCursor::workingCursor() );
311 #endif
314 void
315 TagDialog::queryDone( KTRMResultList results, QString error ) //SLOT
317 #if HAVE_TUNEPIMP
319 if ( !error.isEmpty() ) {
320 KMessageBox::sorry( this, i18n( "Tunepimp (MusicBrainz tagging library) returned the following error: \"%1\".", error) );
322 else {
323 if ( !results.isEmpty() )
325 TrackPickerDialog* t = new TrackPickerDialog( m_mbTrack.fileName(), results, this );
326 t->show();
327 connect( t, SIGNAL( finished() ), SLOT( resetMusicbrainz() ) ); // clear m_mbTrack
329 else {
330 KMessageBox::sorry( this, i18n( "The track was not found in the MusicBrainz database." ) );
331 resetMusicbrainz(); // clear m_mbTrack
335 QApplication::restoreOverrideCursor();
336 pushButton_musicbrainz->setEnabled( true );
337 pushButton_musicbrainz->setText( m_buttonMbText );
338 #else
339 Q_UNUSED(results);
340 Q_UNUSED(error);
341 #endif
344 void
345 TagDialog::fillSelected( KTRMResult selected ) //SLOT
347 #if HAVE_TUNEPIMP
348 kDebug() << k_funcinfo << endl;
351 if ( m_bundle.url() == m_mbTrack ) {
352 if ( !selected.title().isEmpty() ) kLineEdit_title->setText( selected.title() );
353 if ( !selected.artist().isEmpty() ) kComboBox_artist->setCurrentText( selected.artist() );
354 if ( !selected.album().isEmpty() ) kComboBox_album->setCurrentText( selected.album() );
355 if ( selected.track() != 0 ) qSpinBox_track->setValue( selected.track() );
356 if ( selected.year() != 0 ) qSpinBox_year->setValue( selected.year() );
357 } else {
358 MetaBundle mb;
359 mb.setPath( m_mbTrack.path() );
360 if ( !selected.title().isEmpty() ) mb.setTitle( selected.title() );
361 if ( !selected.artist().isEmpty() ) mb.setArtist( selected.artist() );
362 if ( !selected.album().isEmpty() ) mb.setAlbum( selected.album() );
363 if ( selected.track() != 0 ) mb.setTrack( selected.track() );
364 if ( selected.year() != 0 ) mb.setYear( selected.year() );
366 storedTags.replace( m_mbTrack.path(), mb );
368 #else
369 Q_UNUSED(selected);
370 #endif
373 void TagDialog::resetMusicbrainz() //SLOT
375 #if HAVE_TUNEPIMP
376 m_mbTrack = "";
377 #endif
380 ////////////////////////////////////////////////////////////////////////////////
381 // PRIVATE
382 ////////////////////////////////////////////////////////////////////////////////
384 void TagDialog::init()
386 DEBUG_BLOCK
388 // delete itself when closing
389 setAttribute( Qt::WA_DeleteOnClose );
391 KConfigGroup config = Amarok::config( "TagDialog" );
393 kTabWidget->addTab( summaryTab, i18n( "Summary" ) );
394 kTabWidget->addTab( tagsTab, i18n( "Tags" ) );
395 kTabWidget->addTab( lyricsTab, i18n( "Lyrics" ) );
396 kTabWidget->addTab( statisticsTab, i18n( "Statistics" ) );
397 kTabWidget->addTab( labelsTab, i18n( "Labels" ) );
398 kTabWidget->setCurrentPage( config.readEntry( "CurrentTab", 0 ) );
400 const QStringList artists = CollectionDB::instance()->artistList();
401 kComboBox_artist->insertStringList( artists );
402 kComboBox_artist->completionObject()->insertItems( artists );
403 kComboBox_artist->completionObject()->setIgnoreCase( true );
404 kComboBox_artist->setCompletionMode( KGlobalSettings::CompletionPopup );
406 const QStringList albums = CollectionDB::instance()->albumList();
407 kComboBox_album->insertStringList( albums );
408 kComboBox_album->completionObject()->insertItems( albums );
409 kComboBox_album->completionObject()->setIgnoreCase( true );
410 kComboBox_album->setCompletionMode( KGlobalSettings::CompletionPopup );
412 const QStringList composers = CollectionDB::instance()->composerList();
413 kComboBox_composer->insertStringList( composers );
414 kComboBox_composer->completionObject()->insertItems( composers );
415 kComboBox_composer->completionObject()->setIgnoreCase( true );
416 kComboBox_composer->setCompletionMode( KGlobalSettings::CompletionPopup );
418 kComboBox_rating->insertStringList( MetaBundle::ratingList() );
420 // const QStringList genres = MetaBundle::genreList();
421 const QStringList genres = CollectionDB::instance()->genreList();
422 kComboBox_genre->insertStringList( genres );
423 kComboBox_genre->completionObject()->insertItems( genres );
424 kComboBox_genre->completionObject()->setIgnoreCase( true );
426 const QStringList labels = CollectionDB::instance()->labelList();
427 //TODO: figure out a way to add auto-completion support to kTestEdit_selectedLabels
429 //m_labelCloud = new KHTMLPart( labels_favouriteLabelsFrame );
430 m_labelCloud = new HTMLView( labels_favouriteLabelsFrame );
431 m_labelCloud->view()->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored, false );
432 //m_labelCloud->view()->setVScrollBarMode( Q3ScrollView::AlwaysOff );
433 //m_labelCloud->view()->setHScrollBarMode( Q3ScrollView::AlwaysOff );
435 new QVBoxLayout( labels_favouriteLabelsFrame );
436 labels_favouriteLabelsFrame->layout()->add( m_labelCloud->view() );
437 const QStringList favoriteLabels = CollectionDB::instance()->favoriteLabels();
438 QString html = generateHTML( favoriteLabels );
439 m_labelCloud->set( html );
440 connect( m_labelCloud->browserExtension(), SIGNAL( openUrlRequest( const KUrl &, const KParts::URLArgs & ) ),
441 this, SLOT( openUrlRequest( const KUrl & ) ) );
443 // looks better to have a blank label than 0, we can't do this in
444 // the UI file due to bug in Designer
445 qSpinBox_track->setSpecialValueText( " " );
446 qSpinBox_year->setSpecialValueText( " " );
447 qSpinBox_score->setSpecialValueText( " " );
448 qSpinBox_discNumber->setSpecialValueText( " " );
450 if( !AmarokConfig::useRatings() )
452 kComboBox_rating->hide();
453 ratingLabel->hide();
455 if( !AmarokConfig::useScores() )
457 qSpinBox_score->hide();
458 scoreLabel->hide();
461 //HACK due to deficiency in Qt that will be addressed in version 4
462 // QSpinBox doesn't emit valueChanged if you edit the value with
463 // the lineEdit until you change the keyboard focus
464 connect( qSpinBox_year, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
465 connect( qSpinBox_track, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
466 connect( qSpinBox_score, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
467 connect( qSpinBox_discNumber, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
469 // Connects for modification check
470 connect( kLineEdit_title, SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
471 connect( kComboBox_composer,SIGNAL(activated( int )), SLOT(checkModified()) );
472 connect( kComboBox_composer,SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
473 connect( kComboBox_artist, SIGNAL(activated( int )), SLOT(checkModified()) );
474 connect( kComboBox_artist, SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
475 connect( kComboBox_album, SIGNAL(activated( int )), SLOT(checkModified()) );
476 connect( kComboBox_album, SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
477 connect( kComboBox_genre, SIGNAL(activated( int )), SLOT(checkModified()) );
478 connect( kComboBox_genre, SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
479 connect( kComboBox_rating, SIGNAL(activated( int )), SLOT(checkModified()) );
480 connect( kComboBox_rating, SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
481 connect( qSpinBox_track, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
482 connect( qSpinBox_year, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
483 connect( qSpinBox_score, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
484 connect( kTextEdit_comment, SIGNAL(textChanged()), SLOT(checkModified()) );
485 connect( kTextEdit_lyrics, SIGNAL(textChanged()), SLOT(checkModified()) );
486 connect( kTextEdit_selectedLabels, SIGNAL(textChanged()), SLOT(checkModified()) );
488 // Remember original button text
489 m_buttonMbText = pushButton_musicbrainz->text();
491 connect( pushButton_cancel, SIGNAL(clicked()), SLOT(cancelPressed()) );
492 connect( pushButton_ok, SIGNAL(clicked()), SLOT(accept()) );
493 connect( pushButton_open, SIGNAL(clicked()), SLOT(openPressed()) );
494 connect( pushButton_previous, SIGNAL(clicked()), SLOT(previousTrack()) );
495 connect( pushButton_next, SIGNAL(clicked()), SLOT(nextTrack()) );
496 connect( checkBox_perTrack, SIGNAL(clicked()), SLOT(perTrack()) );
498 // set an icon for the open-in-konqui button
499 pushButton_open->setIconSet( KIcon( Amarok::icon( "files" ) ) );
501 //Update lyrics on Context Browser
502 connect( this, SIGNAL(lyricsChanged( const QString& )), ContextBrowser::instance(), SLOT( lyricsChanged( const QString& ) ) );
504 //Update cover
505 connect( CollectionDB::instance(), SIGNAL( coverFetched( const QString&, const QString& ) ),
506 this, SLOT( loadCover( const QString&, const QString& ) ) );
507 connect( CollectionDB::instance(), SIGNAL( coverChanged( const QString&, const QString& ) ),
508 this, SLOT( loadCover( const QString&, const QString& ) ) );
512 #if HAVE_TUNEPIMP
513 connect( pushButton_musicbrainz, SIGNAL(clicked()), SLOT(musicbrainzQuery()) );
514 #else
515 pushButton_musicbrainz->setToolTip( i18n("Please install MusicBrainz to enable this functionality") );
516 #endif
518 connect( pushButton_guessTags, SIGNAL(clicked()), SLOT( guessFromFilename() ) );
519 connect( pushButton_setFilenameSchemes, SIGNAL(clicked()), SLOT( setFileNameSchemes() ) );
521 if( m_urlList.count() ) { //editing multiple tracks
522 m_perTrack = false;
523 setMultipleTracksMode();
524 readMultipleTracks();
526 checkBox_perTrack->setChecked( m_perTrack );
527 if( m_urlList.count() == 1 )
529 checkBox_perTrack->setEnabled( false );
530 pushButton_previous->setEnabled( false );
531 pushButton_next->setEnabled( false );
533 else
535 checkBox_perTrack->setEnabled( true );
536 pushButton_previous->setEnabled( m_perTrack );
537 pushButton_next->setEnabled( m_perTrack );
540 else
542 m_perTrack = true;
543 checkBox_perTrack->hide();
545 if( !m_playlistItem ) {
546 //We have already loaded the metadata (from the file) in the constructor
547 pushButton_previous->hide();
548 pushButton_next->hide();
550 else
552 //Reload the metadata from the file, to be sure it's accurate
553 loadTags( m_playlistItem->url() );
556 loadLyrics( m_bundle.url() );
557 loadLabels( m_bundle.url() );
558 readTags();
562 // make it as small as possible
563 resize( sizeHint().width(), minimumSize().height() );
568 inline const QString TagDialog::unknownSafe( QString s ) {
569 return ( s.isNull() || s.isEmpty() || s == "?" || s == "-" )
570 ? i18n ( "Unknown" )
571 : s;
574 const QStringList TagDialog::statisticsData() {
576 QStringList data, values;
577 const uint artist_id = CollectionDB::instance()->artistID( m_bundle.artist() );
578 const uint album_id = CollectionDB::instance()->albumID ( m_bundle.album() );
580 QueryBuilder qb;
582 if ( !m_bundle.artist().isEmpty() ) {
583 // tracks by this artist
584 qb.clear();
585 qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabSong, QueryBuilder::valTitle );
586 qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, QString::number( artist_id ) );
587 values = qb.run();
588 data += i18n( "Tracks by this Artist" );
589 data += values[0];
592 // albums by this artist
593 qb.clear();
594 qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabAlbum, QueryBuilder::valID );
595 qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, QString::number( artist_id ) );
596 qb.groupBy( QueryBuilder::tabSong, QueryBuilder::valAlbumID );
597 qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) );
598 qb.setOptions( QueryBuilder::optNoCompilations );
599 values = qb.run();
600 data += i18n( "Albums by this Artist" );
601 data += QString::number( values.count() );
604 // Favorite track by this artist
605 qb.clear();
606 qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
607 qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore );
608 qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, QString::number( artist_id ) );
609 qb.sortByFavorite();
610 qb.setLimit( 0, 1 );
611 values = qb.run();
612 data += i18n( "Favorite by this Artist" );
613 data += values.isEmpty() ? QString() : values[0];
615 if ( !m_bundle.album().isEmpty() ) {
616 // Favorite track on this album
617 qb.clear();
618 qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
619 qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore );
620 qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, QString::number( album_id ) );
621 qb.sortByFavorite();
622 qb.setLimit( 0, 1 );
623 values = qb.run();
624 data += i18n( "Favorite on this Album" );
625 data += values.isEmpty() ? QString() : values[0];
628 // Related Artists
629 const QString sArtists = CollectionDB::instance()->similarArtists( m_bundle.artist(), 4 ).join(", ");
630 if ( !sArtists.isEmpty() ) {
631 data += i18n( "Related Artists" );
632 data += sArtists;
635 return data;
638 void TagDialog::readTags()
640 bool local = m_bundle.url().isLocalFile();
642 setCaption( KDialog::makeStandardCaption( i18n("Track Information: %1 by %2", m_bundle.title(), m_bundle.artist() ) ) );
644 QString niceTitle;
645 if ( m_bundle.album().isEmpty() ) {
646 if( !m_bundle.title().isEmpty() ) {
647 if( !m_bundle.artist().isEmpty() )
648 niceTitle = i18n( "<b>%1</b> by <b>%2</b>", m_bundle.title(), m_bundle.artist() );
649 else
650 niceTitle = QString( "<b>%1</b>").arg( m_bundle.title() );
652 else niceTitle = m_bundle.prettyTitle();
654 else {
655 niceTitle = i18n( "<b>%1</b> by <b>%2</b> on <b>%3</b>" ,
656 m_bundle.title(), m_bundle.artist(), m_bundle.album() );
658 trackArtistAlbumLabel->setText( niceTitle );
659 trackArtistAlbumLabel2->setText( niceTitle );
661 kLineEdit_title ->setText( m_bundle.title() );
662 kComboBox_artist ->setCurrentText( m_bundle.artist() );
663 kComboBox_album ->setCurrentText( m_bundle.album() );
664 kComboBox_genre ->setCurrentText( m_bundle.genre() );
665 kComboBox_rating ->setCurrentIndex( m_bundle.rating() ? m_bundle.rating() - 1 : 0 );
666 qSpinBox_track ->setValue( m_bundle.track() );
667 kComboBox_composer ->setCurrentText( m_bundle.composer() );
668 qSpinBox_year ->setValue( m_bundle.year() );
669 qSpinBox_score ->setValue( static_cast<int>(m_bundle.score()) );
670 qSpinBox_discNumber ->setValue( m_bundle.discNumber() );
671 kTextEdit_comment ->setText( m_bundle.comment() );
673 bool extended = m_bundle.hasExtendedMetaInformation();
674 qSpinBox_discNumber->setEnabled( extended );
675 kComboBox_composer->setEnabled( extended );
678 QString summaryText, statisticsText;
679 const QString body2cols = "<tr><td><nobr>%1</nobr></td><td><b>%2</b></td></tr>";
680 const QString body1col = "<tr><td colspan=2>%1</td></td></tr>";
681 const QString emptyLine = "<tr><td colspan=2></td></tr>";
683 summaryText = "<table width=100%><tr><td width=50%><table>";
684 summaryText += body2cols.arg( i18n("Length:"), unknownSafe( m_bundle.prettyLength() ) );
685 summaryText += body2cols.arg( i18n("Bitrate:"), unknownSafe( m_bundle.prettyBitrate() ) );
686 summaryText += body2cols.arg( i18n("Samplerate:"), unknownSafe( m_bundle.prettySampleRate() ) );
687 summaryText += body2cols.arg( i18n("Size:"), unknownSafe( m_bundle.prettyFilesize() ) );
688 summaryText += body2cols.arg( i18n("Format:"), unknownSafe( m_bundle.type() ) );
690 summaryText += "</table></td><td width=50%><table>";
691 if( AmarokConfig::useScores() )
692 summaryText += body2cols.arg( i18n("Score:"), QString::number( static_cast<int>( m_bundle.score() ) ) );
693 if( AmarokConfig::useRatings() )
694 summaryText += body2cols.arg( i18n("Rating:"), m_bundle.prettyRating() );
696 summaryText += body2cols.arg( i18n("Playcount:"), QString::number( m_bundle.playCount() ) );
697 summaryText += body2cols.arg( i18n("First Played:"),
698 m_bundle.playCount() ? KGlobal::locale()->formatDate( CollectionDB::instance()->getFirstPlay( m_bundle.url().path() ).date() , KLocale::ShortDate ) : i18n("Never") );
699 summaryText += body2cols.arg( i18nc("a single item (singular)", "Last Played:"),
700 m_bundle.playCount() ? KGlobal::locale()->formatDate( CollectionDB::instance()->getLastPlay( m_bundle.url().path() ).date() , KLocale::ShortDate ) : i18n("Never") );
702 summaryText += "</table></td></tr></table>";
703 summaryLabel->setText( summaryText );
705 statisticsText = "<table>";
707 QStringList sData = statisticsData();
708 for ( uint i = 0; i<sData.count(); i+=2 ) {
709 statisticsText += body2cols.arg( sData[i], sData[i+1] );
712 statisticsText += "</table>";
714 statisticsLabel->setText( statisticsText );
716 kLineEdit_location->setText( local ? m_bundle.url().path() : m_bundle.url().url() );
718 //lyrics
719 kTextEdit_lyrics->setText( m_lyrics );
721 loadCover( m_bundle.artist(), m_bundle.album() );
724 // enable only for local files
725 kLineEdit_title->setReadOnly( !local );
726 kComboBox_artist->setEnabled( local );
727 kComboBox_album->setEnabled( local );
728 kComboBox_genre->setEnabled( local );
729 kComboBox_rating->setEnabled( local );
730 qSpinBox_track->setEnabled( local );
731 qSpinBox_year->setEnabled( local );
732 qSpinBox_score->setEnabled( local );
733 kTextEdit_comment->setEnabled( local );
734 kTextEdit_selectedLabels->setEnabled( local );
735 m_labelCloud->view()->setEnabled( local );
737 if( local )
739 pushButton_musicbrainz->show();
740 pushButton_guessTags->show();
741 pushButton_setFilenameSchemes->show();
743 else
745 pushButton_musicbrainz->hide();
746 pushButton_guessTags->hide();
747 pushButton_setFilenameSchemes->hide();
750 // If it's a local file, write the directory to m_path, else disable the "open in konqui" button
751 if ( local )
752 m_path = m_bundle.url().directory();
753 else
754 pushButton_open->setEnabled( false );
756 pushButton_ok->setEnabled( storedTags.count() > 0 || storedScores.count() > 0
757 || storedLyrics.count() > 0 || storedRatings.count() > 0
758 || newLabels.count() > 0 );
760 #if HAVE_TUNEPIMP
761 // Don't enable button if a query is in progress already (or if the file isn't local)
762 pushButton_musicbrainz->setEnabled( m_bundle.url().isLocalFile() && m_mbTrack.isEmpty() );
763 #else
764 pushButton_musicbrainz->setEnabled( false );
765 #endif
767 if( m_playlistItem ) {
768 pushButton_previous->setEnabled( m_playlistItem->itemAbove() );
769 pushButton_next->setEnabled( m_playlistItem->itemBelow() );
774 void
775 TagDialog::setMultipleTracksMode()
778 kTabWidget->setTabEnabled( summaryTab, false );
779 kTabWidget->setTabEnabled( lyricsTab, false );
781 kComboBox_artist->setCurrentText( "" );
782 kComboBox_album->setCurrentText( "" );
783 kComboBox_genre->setCurrentText( "" );
784 kComboBox_composer->setCurrentText( "" );
785 kLineEdit_title->setText( "" );
786 kTextEdit_comment->setText( "" );
787 qSpinBox_track->setValue( qSpinBox_track->minimum() );
788 qSpinBox_discNumber->setValue( qSpinBox_discNumber->minimum() );
789 qSpinBox_year->setValue( qSpinBox_year->minimum() );
791 qSpinBox_score->setValue( qSpinBox_score->minimum() );
792 kComboBox_rating->setCurrentItem( 0 );
794 kLineEdit_title->setEnabled( false );
795 qSpinBox_track->setEnabled( false );
797 pushButton_musicbrainz->hide();
798 pushButton_guessTags->hide();
799 pushButton_setFilenameSchemes->hide();
801 locationLabel->hide();
802 kLineEdit_location->hide();
803 pushButton_open->hide();
804 pixmap_cover->hide();
807 void
808 TagDialog::setSingleTrackMode()
811 kTabWidget->setTabEnabled( summaryTab, true );
812 kTabWidget->setTabEnabled( lyricsTab, true );
814 kLineEdit_title->setEnabled( true );
815 qSpinBox_track->setEnabled( true );
817 pushButton_musicbrainz->show();
818 pushButton_guessTags->show();
819 pushButton_setFilenameSchemes->show();
821 locationLabel->show();
822 kLineEdit_location->show();
823 pushButton_open->show();
824 pixmap_cover->show();
828 void
829 TagDialog::readMultipleTracks()
832 setCaption( KDialog::makeStandardCaption( i18np("1 Track", "Information for %1 Tracks", m_urlList.count()) ) );
834 //Check which fields are the same for all selected tracks
835 const KUrl::List::ConstIterator end = m_urlList.end();
836 KUrl::List::ConstIterator it = m_urlList.begin();
838 m_bundle = MetaBundle();
840 MetaBundle first = bundleForURL( *it );
842 bool artist=true, album=true, genre=true, comment=true, year=true,
843 score=true, rating=true, composer=true, discNumber=true;
844 int songCount=0, ratingCount=0, ratingSum=0, scoreCount=0;
845 float scoreSum = 0.f;
846 for ( ; it != end; ++it ) {
847 MetaBundle mb = bundleForURL( *it );
848 songCount++;
849 if ( mb.rating() ) {
850 ratingCount++;
851 ratingSum+=mb.rating();
853 if ( mb.score() > 0.f ) {
854 scoreCount++;
855 scoreSum+=mb.score();
858 if( !mb.url().isLocalFile() ) {
859 // If we have a non local file, don't even lose more time comparing
860 artist = album = genre = comment = year = false;
861 score = rating = composer = discNumber = false;
862 continue;
864 if ( artist && mb.artist()!=first.artist() )
865 artist=false;
866 if ( album && mb.album()!=first.album() )
867 album=false;
868 if ( genre && mb.genre()!=first.genre() )
869 genre=false;
870 if ( comment && mb.comment()!=first.comment() )
871 comment=false;
872 if ( year && mb.year()!=first.year() )
873 year=false;
874 if ( composer && mb.composer()!=first.composer() )
875 composer=false;
876 if ( discNumber && mb.discNumber()!=first.discNumber() )
877 discNumber=false;
878 if ( score && mb.score()!=first.score() )
879 score = false;
880 if ( rating && mb.rating()!=first.rating() )
881 rating = false;
883 // Set them in the dialog and in m_bundle ( so we don't break hasChanged() )
884 if (artist) {
885 m_bundle.setArtist( first.artist() );
886 kComboBox_artist->setCurrentText( first.artist() );
888 if (album) {
889 m_bundle.setAlbum( first.album() );
890 kComboBox_album->setCurrentText( first.album() );
892 if (genre) {
893 m_bundle.setGenre( first.genre() );
894 kComboBox_genre->setCurrentText( first.genre() );
896 if (comment) {
897 m_bundle.setComment( first.comment() );
898 kTextEdit_comment->setText( first.comment() );
900 if (composer) {
901 m_bundle.setComposer( first.composer() );
902 kComboBox_composer->setCurrentText( first.composer() );
904 if (year) {
905 m_bundle.setYear( first.year() );
906 qSpinBox_year->setValue( first.year() );
908 if (discNumber) {
909 m_bundle.setDiscNumber( first.discNumber() );
910 qSpinBox_discNumber->setValue( first.discNumber() );
912 if (score) {
913 m_bundle.setScore( first.score() );
914 qSpinBox_score->setValue( static_cast<int>( first.score() ) );
916 if (rating) {
917 m_bundle.setRating( first.rating() );
918 kComboBox_rating->setCurrentIndex( first.rating() ? first.rating() - 1 : 0 );
921 m_currentURL = m_urlList.begin();
923 trackArtistAlbumLabel2->setText( i18np( "Editing 1 file", "Editing %1 files", songCount ) );
925 const QString body = "<tr><td><nobr>%1:</nobr></td><td><b>%2</b></td></tr>";
926 QString statisticsText = "<table>";
928 if( AmarokConfig::useRatings() ) {
929 statisticsText += body.arg( i18n( "Rated Songs:" ) , QString::number( ratingCount ) );
930 if ( ratingCount )
931 statisticsText += body.arg( i18n( "Average Rating:" ) , QString::number( (float)ratingSum / (float)ratingCount/2.0, 'f', 1 ) );
934 if( AmarokConfig::useRatings() ) {
935 statisticsText += body.arg( i18n( "Scored Songs:" ) , QString::number( scoreCount ) );
936 if ( scoreCount )
937 statisticsText += body.arg( i18n( "Average Score:" ) , QString::number( scoreSum / scoreCount, 'f', 1 ) );
941 statisticsText += "</table>";
943 statisticsLabel->setText( statisticsText );
945 QStringList commonLabels = getCommonLabels();
946 QString text;
947 oldForeach ( commonLabels )
949 if ( !text.isEmpty() )
950 text.append( ", " );
951 text.append( *it );
953 kTextEdit_selectedLabels->setText( text );
954 m_commaSeparatedLabels = text;
956 // This will reset a wrongly enabled Ok button
957 checkModified();
960 QStringList
961 TagDialog::getCommonLabels()
963 DEBUG_BLOCK
964 QMap<QString, int> counterMap;
965 const KUrl::List::ConstIterator end = m_urlList.end();
966 KUrl::List::ConstIterator iter = m_urlList.begin();
967 for(; iter != end; ++iter )
969 QStringList labels = labelsForURL( *iter );
970 oldForeach( labels )
972 if ( counterMap.contains( *it ) )
973 counterMap[ *it ] = counterMap[ *it ] +1;
974 else
975 counterMap[ *it ] = 1;
978 int n = m_urlList.count();
979 QStringList result;
980 QMap<QString, int>::ConstIterator counterEnd( counterMap.end() );
981 for(QMap<QString, int>::ConstIterator it = counterMap.begin(); it != counterEnd; ++it )
983 if ( it.data() == n )
984 result.append( it.key() );
986 return result;
989 inline bool
990 equalString( const QString &a, const QString &b )
992 return (a.isEmpty() && b.isEmpty()) ? true : a == b;
995 bool
996 TagDialog::hasChanged()
998 return changes();
1002 TagDialog::changes()
1004 int result=TagDialog::NOCHANGE;
1005 bool modified = false;
1006 modified |= !equalString( kComboBox_artist->lineEdit()->text(), m_bundle.artist() );
1007 modified |= !equalString( kComboBox_album->lineEdit()->text(), m_bundle.album() );
1008 modified |= !equalString( kComboBox_genre->lineEdit()->text(), m_bundle.genre() );
1009 modified |= qSpinBox_year->value() != m_bundle.year();
1010 modified |= qSpinBox_discNumber->value() != m_bundle.discNumber();
1011 modified |= !equalString( kComboBox_composer->lineEdit()->text(), m_bundle.composer() );
1013 modified |= !equalString( kTextEdit_comment->text(), m_bundle.comment() );
1015 if (!m_urlList.count() || m_perTrack) { //ignore these on MultipleTracksMode
1016 modified |= !equalString( kLineEdit_title->text(), m_bundle.title() );
1017 modified |= qSpinBox_track->value() != m_bundle.track();
1019 if (modified)
1020 result |= TagDialog::TAGSCHANGED;
1022 if (qSpinBox_score->value() != m_bundle.score())
1023 result |= TagDialog::SCORECHANGED;
1024 if (kComboBox_rating->currentIndex() != ( m_bundle.rating() ? m_bundle.rating() - 1 : 0 ) )
1025 result |= TagDialog::RATINGCHANGED;
1027 if (!m_urlList.count() || m_perTrack) { //ignore these on MultipleTracksMode
1028 if ( !equalString( kTextEdit_lyrics->text(), m_lyrics ) )
1029 result |= TagDialog::LYRICSCHANGED;
1032 if ( !equalString( kTextEdit_selectedLabels->text(), m_commaSeparatedLabels ) )
1033 result |= TagDialog::LABELSCHANGED;
1035 return result;
1038 void
1039 TagDialog::storeTags()
1041 storeTags( m_bundle.url() );
1044 void
1045 TagDialog::storeTags( const KUrl &kurl )
1047 int result = changes();
1048 QString url = kurl.path();
1049 if( result & TagDialog::TAGSCHANGED ) {
1050 MetaBundle mb( m_bundle );
1052 mb.setTitle( kLineEdit_title->text() );
1053 mb.setComposer( kComboBox_composer->currentText() );
1054 mb.setArtist( kComboBox_artist->currentText() );
1055 mb.setAlbum( kComboBox_album->currentText() );
1056 mb.setComment( kTextEdit_comment->text() );
1057 mb.setGenre( kComboBox_genre->currentText() );
1058 mb.setTrack( qSpinBox_track->value() );
1059 mb.setYear( qSpinBox_year->value() );
1060 mb.setDiscNumber( qSpinBox_discNumber->value() );
1061 mb.setLength( m_bundle.length() );
1062 mb.setBitrate( m_bundle.bitrate() );
1063 mb.setSampleRate( m_bundle.sampleRate() );
1064 storedTags.replace( url, mb );
1066 if( result & TagDialog::SCORECHANGED )
1067 storedScores.replace( url, qSpinBox_score->value() );
1068 if( result & TagDialog::RATINGCHANGED )
1069 storedRatings.replace( url, kComboBox_rating->currentIndex() ? kComboBox_rating->currentIndex() + 1 : 0 );
1070 if( result & TagDialog::LYRICSCHANGED ) {
1071 if ( kTextEdit_lyrics->text().isEmpty() )
1072 storedLyrics.replace( url, QString() );
1073 else {
1074 QDomDocument doc;
1075 QDomElement e = doc.createElement( "lyrics" );
1076 e.setAttribute( "artist", kComboBox_artist->currentText() );
1077 e.setAttribute( "title", kLineEdit_title->text() );
1078 QDomText t = doc.createTextNode( kTextEdit_lyrics->text() );
1079 e.appendChild( t );
1080 doc.appendChild( e );
1081 storedLyrics.replace( url, doc.toString() );
1084 if( result & TagDialog::LABELSCHANGED ) {
1085 generateDeltaForLabelList( labelListFromText( kTextEdit_selectedLabels->text() ) );
1086 QStringList tmpLabels;
1087 if ( newLabels.find( url ) != newLabels.end() )
1088 tmpLabels = newLabels[ url ];
1089 else
1090 tmpLabels = originalLabels[ url ];
1091 //apply delta
1092 oldForeach( m_removedLabels )
1094 tmpLabels.remove( *it );
1096 oldForeach( m_addedLabels )
1098 if( tmpLabels.find( *it ) == tmpLabels.end() )
1099 tmpLabels.append( *it );
1101 newLabels.replace( url, tmpLabels );
1105 void
1106 TagDialog::storeTags( const KUrl &url, int changes, const MetaBundle &mb )
1108 if ( changes & TagDialog::TAGSCHANGED )
1109 storedTags.replace( url.path(), mb );
1110 if ( changes & TagDialog::SCORECHANGED )
1111 storedScores.replace( url.path(), mb.score() );
1112 if ( changes & TagDialog::RATINGCHANGED )
1113 storedRatings.replace( url.path(), mb.rating() );
1116 void
1117 TagDialog::storeLabels( const KUrl &url, const QStringList &labels )
1119 newLabels.replace( url.path(), labels );
1123 void
1124 TagDialog::loadTags( const KUrl &url )
1126 m_bundle = bundleForURL( url );
1127 loadLyrics( url );
1128 loadLabels( url );
1131 void
1132 TagDialog::loadLyrics( const KUrl &url )
1134 QString xml = lyricsForURL(url.path() );
1136 QDomDocument doc;
1137 if( doc.setContent( xml ) )
1138 m_lyrics = doc.documentElement().text();
1139 else
1140 m_lyrics.clear();
1143 void
1144 TagDialog::loadLabels( const KUrl &url )
1146 DEBUG_BLOCK
1147 m_labels = labelsForURL( url );
1148 originalLabels[ url.path() ] = m_labels;
1149 QString text;
1150 oldForeach( m_labels )
1152 if ( !text.isEmpty() )
1153 text.append( ", " );
1154 text.append( *it );
1156 kTextEdit_selectedLabels->setText( text );
1157 m_commaSeparatedLabels = text;
1160 MetaBundle
1161 TagDialog::bundleForURL( const KUrl &url )
1163 if( storedTags.find( url.path() ) != storedTags.end() )
1164 return storedTags[ url.path() ];
1166 return MetaBundle( url, url.isLocalFile() );
1169 float
1170 TagDialog::scoreForURL( const KUrl &url )
1172 if( storedScores.find( url.path() ) != storedScores.end() )
1173 return storedScores[ url.path() ];
1175 return CollectionDB::instance()->getSongPercentage( url.path() );
1179 TagDialog::ratingForURL( const KUrl &url )
1181 if( storedRatings.find( url.path() ) != storedRatings.end() )
1182 return storedRatings[ url.path() ];
1184 return CollectionDB::instance()->getSongRating( url.path() );
1187 QString
1188 TagDialog::lyricsForURL( const KUrl &url )
1190 if( storedLyrics.find( url.path() ) != storedLyrics.end() )
1191 return storedLyrics[ url.path() ];
1193 return CollectionDB::instance()->getLyrics( url.path() );
1196 QStringList
1197 TagDialog::labelsForURL( const KUrl &url )
1199 if( newLabels.find( url.path() ) != newLabels.end() )
1200 return newLabels[ url.path() ];
1201 if( originalLabels.find( url.path() ) != originalLabels.end() )
1202 return originalLabels[ url.path() ];
1203 QStringList tmp = CollectionDB::instance()->getLabels( url.path(), CollectionDB::typeUser );
1204 originalLabels[ url.path() ] = tmp;
1205 return tmp;
1208 void
1209 TagDialog::saveTags()
1211 if( !m_perTrack )
1213 applyToAllTracks();
1215 else
1217 storeTags();
1220 QMap<QString, float>::ConstIterator endScore( storedScores.end() );
1221 for(QMap<QString, float>::ConstIterator it = storedScores.begin(); it != endScore; ++it ) {
1222 CollectionDB::instance()->setSongPercentage( it.key(), it.data() );
1224 QMap<QString, int>::ConstIterator endRating( storedRatings.end() );
1225 for(QMap<QString, int>::ConstIterator it = storedRatings.begin(); it != endRating; ++it ) {
1226 CollectionDB::instance()->setSongRating( it.key(), it.data() );
1228 QMap<QString, QString>::ConstIterator endLyrics( storedLyrics.end() );
1229 for(QMap<QString, QString>::ConstIterator it = storedLyrics.begin(); it != endLyrics; ++it ) {
1230 CollectionDB::instance()->setLyrics( it.key(), it.data(),
1231 CollectionDB::instance()->uniqueIdFromUrl( KUrl( it.key() ) ) );
1232 emit lyricsChanged( it.key() );
1234 QMap<QString, QStringList>::ConstIterator endLabels( newLabels.end() );
1235 for(QMap<QString, QStringList>::ConstIterator it = newLabels.begin(); it != endLabels; ++it ) {
1236 CollectionDB::instance()->setLabels( it.key(), it.data(),
1237 CollectionDB::instance()->uniqueIdFromUrl( KUrl( it.key() ) ), CollectionDB::typeUser );
1239 CollectionDB::instance()->cleanLabels();
1241 ThreadManager::instance()->queueJob( new TagDialogWriter( storedTags ) );
1245 void
1246 TagDialog::applyToAllTracks()
1248 generateDeltaForLabelList( labelListFromText( kTextEdit_selectedLabels->text() ) );
1250 const KUrl::List::ConstIterator end = m_urlList.end();
1251 for ( KUrl::List::ConstIterator it = m_urlList.begin(); it != end; ++it ) {
1253 /* we have to update the values if they changed, so:
1254 1) !kLineEdit_field->text().isEmpty() && kLineEdit_field->text() != mb.field
1255 i.e.: The user wrote something on the field, and it's different from
1256 what we have in the tag.
1257 2) !m_bundle.field().isEmpty() && kLineEdit_field->text().isEmpty()
1258 i.e.: The user was shown some value for the field (it was the same
1259 for all selected tracks), and he deliberately emptied it.
1260 TODO: All this mess is because the dialog uses "" to represent what the user
1261 doesn't want to change, maybe we can think of something better?
1264 MetaBundle mb = bundleForURL( *it );
1266 int changed = 0;
1267 if( !kComboBox_artist->currentText().isEmpty() && kComboBox_artist->currentText() != mb.artist() ||
1268 kComboBox_artist->currentText().isEmpty() && !m_bundle.artist().isEmpty() ) {
1269 mb.setArtist( kComboBox_artist->currentText() );
1270 changed |= TagDialog::TAGSCHANGED;
1273 if( !kComboBox_album->currentText().isEmpty() && kComboBox_album->currentText() != mb.album() ||
1274 kComboBox_album->currentText().isEmpty() && !m_bundle.album().isEmpty() ) {
1275 mb.setAlbum( kComboBox_album->currentText() );
1276 changed |= TagDialog::TAGSCHANGED;
1278 if( !kComboBox_genre->currentText().isEmpty() && kComboBox_genre->currentText() != mb.genre() ||
1279 kComboBox_genre->currentText().isEmpty() && !m_bundle.genre().isEmpty() ) {
1280 mb.setGenre( kComboBox_genre->currentText() );
1281 changed |= TagDialog::TAGSCHANGED;
1283 if( !kTextEdit_comment->text().isEmpty() && kTextEdit_comment->text() != mb.comment() ||
1284 kTextEdit_comment->text().isEmpty() && !m_bundle.comment().isEmpty() ) {
1285 mb.setComment( kTextEdit_comment->text() );
1286 changed |= TagDialog::TAGSCHANGED;
1288 if( !kComboBox_composer->currentText().isEmpty() && kComboBox_composer->currentText() != mb.composer() ||
1289 kComboBox_composer->currentText().isEmpty() && !m_bundle.composer().isEmpty() ) {
1290 mb.setComposer( kComboBox_composer->currentText() );
1291 changed |= TagDialog::TAGSCHANGED;
1294 if( qSpinBox_year->value() && qSpinBox_year->value() != mb.year() ||
1295 !qSpinBox_year->value() && m_bundle.year() ) {
1296 mb.setYear( qSpinBox_year->value() );
1297 changed |= TagDialog::TAGSCHANGED;
1299 if( qSpinBox_discNumber->value() && qSpinBox_discNumber->value() != mb.discNumber() ||
1300 !qSpinBox_discNumber->value() && m_bundle.discNumber() ) {
1301 mb.setDiscNumber( qSpinBox_discNumber->value() );
1302 changed |= TagDialog::TAGSCHANGED;
1305 if( qSpinBox_score->value() && qSpinBox_score->value() != mb.score() ||
1306 !qSpinBox_score->value() && m_bundle.score() )
1308 mb.setScore( qSpinBox_score->value() );
1309 changed |= TagDialog::SCORECHANGED;
1312 if( kComboBox_rating->currentIndex() && kComboBox_rating->currentIndex() != m_bundle.rating() - 1 ||
1313 !kComboBox_rating->currentIndex() && m_bundle.rating() )
1315 mb.setRating( kComboBox_rating->currentIndex() ? kComboBox_rating->currentIndex() + 1 : 0 );
1316 changed |= TagDialog::RATINGCHANGED;
1318 storeTags( *it, changed, mb );
1320 QStringList tmpLabels = labelsForURL( *it );
1321 //apply delta
1322 for( QStringList::Iterator iter = m_removedLabels.begin(); iter != m_removedLabels.end(); ++iter )
1324 tmpLabels.remove( *iter );
1326 for( QStringList::Iterator iter = m_addedLabels.begin(); iter != m_addedLabels.end(); ++iter )
1328 if( tmpLabels.find( *iter ) == tmpLabels.end() )
1329 tmpLabels.append( *iter );
1331 storeLabels( *it, tmpLabels );
1335 QStringList
1336 TagDialog::labelListFromText( const QString &text )
1338 QStringList tmp = QStringList::split( ',', text );
1339 //insert each string into a map to remove duplicates
1340 QMap<QString, int> map;
1341 oldForeach( tmp )
1343 QString tmpString = (*it).trimmed();
1344 if ( !tmpString.isEmpty() )
1345 map.replace( tmpString, 0 );
1347 QStringList result;
1348 QMap<QString, int>::ConstIterator endMap( map.end() );
1349 for(QMap<QString, int>::ConstIterator it = map.begin(); it != endMap; ++it ) {
1350 result.append( it.key() );
1352 return result;
1355 void
1356 TagDialog::generateDeltaForLabelList( const QStringList &list )
1358 m_addedLabels.clear();
1359 m_removedLabels.clear();
1360 oldForeach( list )
1362 if ( !m_labels.contains( *it ) )
1363 m_addedLabels.append( *it );
1365 oldForeach( m_labels )
1367 if ( !list.contains( *it ) )
1368 m_removedLabels.append( *it );
1370 m_labels = list;
1373 QString
1374 TagDialog::generateHTML( const QStringList &labels )
1376 //the first column of each row is the label name, the second the number of assigned songs
1377 //loop through it to find the highest number of songs, can be removed if somebody figures out a better sql query
1378 QMap<QString, QPair<QString, int> > mapping;
1379 QStringList sortedLabels;
1380 int max = 1;
1381 oldForeach( labels )
1383 QString label = *it;
1384 sortedLabels << label.toLower();
1385 ++it;
1386 int value = ( *it ).toInt();
1387 if ( value > max )
1388 max = value;
1389 mapping[label.toLower()] = QPair<QString, int>( label, value );
1391 sortedLabels.sort();
1392 QString html = "<html><body>";
1393 oldForeach( sortedLabels )
1395 QString key = *it;
1396 //generate a number in the range 1..10 based on how much the label is used
1397 int labelUse = ( mapping[key].second * 10 ) / max;
1398 if ( labelUse == 0 )
1399 labelUse = 1;
1400 html.append( QString( "<span class='label size%1'><a href=\"label:%2\">%3</a></span> " )
1401 .arg( QString::number( labelUse ), mapping[key].first, mapping[key].first ) );
1403 html.append( "</html></body>" );
1404 debug() << "Dumping HTML for label cloud: " << html << endl;
1405 return html;
1408 void
1409 TagDialog::openUrlRequest(const KUrl &url ) //SLOT
1411 DEBUG_BLOCK
1412 if ( url.protocol() == "label" )
1414 QString text = kTextEdit_selectedLabels->text();
1415 QStringList currentLabels = labelListFromText( text );
1416 if ( currentLabels.contains( url.path() ) )
1417 return;
1418 if ( !text.isEmpty() )
1419 text.append( ", " );
1420 text.append( url.path() );
1421 kTextEdit_selectedLabels->setText( text );
1425 bool
1426 TagDialog::writeTag( MetaBundle &mb, bool updateCB )
1428 QByteArray path = QFile::encodeName( mb.url().path() );
1429 if ( !TagLib::File::isWritable( path ) ) {
1430 Amarok::StatusBar::instance()->longMessage( i18n(
1431 "The file %1 is not writable.", mb.url().fileName() ), KDE::StatusBar::Error );
1432 return false;
1435 //visual feedback
1436 QApplication::setOverrideCursor( Qt::WaitCursor );
1438 bool result = mb.save();
1439 mb.updateFilesize();
1441 if( result )
1442 //update the collection db
1443 CollectionDB::instance()->updateTags( mb.url().path(), mb, updateCB );
1445 QApplication::restoreOverrideCursor();
1447 return result;
1450 TagDialogWriter::TagDialogWriter( const QMap<QString, MetaBundle> tagsToChange )
1451 : ThreadManager::Job( "TagDialogWriter" ),
1452 m_successCount ( 0 ),
1453 m_failCount ( 0 )
1455 QApplication::setOverrideCursor( Qt::WaitCursor );
1456 QMap<QString, MetaBundle>::ConstIterator end = tagsToChange.end();
1457 for(QMap<QString, MetaBundle>::ConstIterator it = tagsToChange.begin(); it != end; ++it ) {
1458 MetaBundle mb = it.data();
1459 m_tags += mb;
1463 bool
1464 TagDialogWriter::doJob()
1466 for( int i = 0, size=m_tags.size(); i<size; ++i ) {
1467 QByteArray path = QFile::encodeName( m_tags[i].url().path() );
1468 if ( !TagLib::File::isWritable( path ) ) {
1469 Amarok::StatusBar::instance()->longMessageThreadSafe( i18n(
1470 "The file %1 is not writable.", m_tags[i].url().fileName() ), KDE::StatusBar::Error );
1471 m_failed += true;
1472 continue;
1475 bool result = m_tags[i].save();
1476 m_tags[i].updateFilesize();
1478 if( result )
1479 m_successCount++;
1480 else {
1481 m_failCount++;
1482 m_failedURLs += m_tags[i].prettyUrl();
1484 m_failed += !result;
1486 return true;
1489 void
1490 TagDialogWriter::completeJob()
1492 for( int i = 0, size=m_tags.size(); i<size; ++i ) {
1493 if ( !m_failed[i] ) {
1494 CollectionDB::instance()->updateTags( m_tags[i].url().path(), m_tags[i], false /* don't update browsers*/ );
1495 Playlist::instance()->updateMetaData( m_tags[i] );
1498 QApplication::restoreOverrideCursor();
1499 if ( m_successCount )
1500 CollectionView::instance()->databaseChanged();
1501 if ( m_failCount )
1502 Amarok::StatusBar::instance()->longMessage( i18n(
1503 "Sorry, the tag for the following files could not be changed:\n", m_failedURLs.join( ";\n" ) ), KDE::StatusBar::Error );
1507 #include "tagdialog.moc"