Make plasma libs build.
[amarok.git] / src / tagdialog.cpp
blobf1aee1f8da33bc8bec29be0d1769a64b9d6b04dc
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.
6 #include "tagdialog.h"
8 #include "amarok.h"
9 #include "amarokconfig.h"
10 #include "CoverFetcher.h"
11 #include "debug.h"
12 #include "querybuilder.h"
13 #include "ContextStatusBar.h" //for status messages
14 #include "TagGuesser.h"
15 #include "trackpickerdialog.h"
16 #include "ui_tagguesserconfigdialog.h"
18 #include <KApplication>
19 #include <KComboBox>
20 #include <KCursor>
21 #include <KGlobal>
22 #include <KHTMLView>
23 #include <KIconLoader>
24 #include <KLineEdit>
25 #include <KMessageBox>
26 #include <KNumInput>
27 #include <KRun>
28 #include <KStandardDirs>
29 #include <KTabWidget>
30 #include <KTextEdit>
31 #include <KVBox>
33 #include <QCheckBox>
34 #include <qdom.h>
35 #include <QFile>
36 #include <QLabel>
37 #include <QLayout>
38 #include <QPair>
39 #include <QPushButton>
40 #include <QToolTip>
42 #include <tfile.h> //TagLib::File::isWritable
44 class TagDialogWriter : public ThreadManager::Job
46 public:
47 TagDialogWriter( const QMap<QString, MetaBundle> tagsToChange );
48 bool doJob();
49 void completeJob();
50 private:
51 QList<bool> m_failed;
52 QList<MetaBundle> m_tags;
53 bool m_updateView;
54 int m_successCount;
55 int m_failCount;
56 QStringList m_failedURLs;
59 TagDialog::TagDialog( const KUrl& url, QWidget* parent )
60 : TagDialogBase( parent )
61 , m_bundle( url, true )
62 , m_playlistItem( 0 )
63 , m_currentCover( 0 )
65 init();
69 TagDialog::TagDialog( const KUrl::List list, QWidget* parent )
70 : TagDialogBase( parent )
71 , m_bundle()
72 , m_playlistItem( 0 )
73 , m_urlList( list )
74 , m_currentCover( 0 )
76 init();
80 // TagDialog::TagDialog( const MetaBundle& mb, PlaylistItem* item, QWidget* parent )
81 // : TagDialogBase( parent )
82 // , m_bundle( mb )
83 // , m_playlistItem( item )
84 // , m_currentCover( 0 )
85 // {
86 // init();
87 // }
89 TagDialog::TagDialog( const Meta::TrackList &tracks, QWidget *parent )
90 :TagDialogBase( parent )
91 , m_currentCover()
92 , m_tracks( tracks )
93 , m_currentTrack( 0 )
95 init();
98 TagDialog::TagDialog( Meta::TrackPtr track, QWidget *parent )
99 :TagDialogBase( parent )
100 , m_currentCover()
101 , m_tracks()
102 , m_currentTrack( track )
104 m_tracks.append( track );
105 init();
109 TagDialog::~TagDialog()
111 DEBUG_BLOCK
113 Amarok::config( "TagDialog" ).writeEntry( "CurrentTab", kTabWidget->currentIndex() );
114 delete m_labelCloud;
117 void
118 TagDialog::setTab( int id )
120 kTabWidget->setCurrentIndex( id );
124 ////////////////////////////////////////////////////////////////////////////////
125 // PRIVATE SLOTS
126 ////////////////////////////////////////////////////////////////////////////////
128 void
129 TagDialog::cancelPressed() //SLOT
131 QApplication::restoreOverrideCursor(); // restore the cursor before closing the dialog
132 reject();
136 void
137 TagDialog::accept() //SLOT
139 pushButton_ok->setEnabled( false ); //visual feedback
140 saveTags();
142 QDialog::accept();
146 inline void
147 TagDialog::openPressed() //SLOT
149 Amarok::invokeBrowser( m_path );
153 inline void
154 TagDialog::previousTrack()
156 //PORT 2.0
157 // if( m_playlistItem )
158 // {
159 // if( !m_playlistItem->itemAbove() ) return;
161 // storeTags();
163 // m_playlistItem = static_cast<PlaylistItem *>( m_playlistItem->itemAbove() );
165 // loadTags( m_playlistItem->url() );
166 // }
167 // else
168 // {
169 storeTags( *m_currentURL );
171 if( m_currentURL != m_urlList.begin() )
172 --m_currentURL;
173 loadTags( *m_currentURL );
174 enableItems();
175 // }
176 readTags();
180 inline void
181 TagDialog::nextTrack()
183 //PORT 2.0
184 // if( m_playlistItem )
185 // {
186 // if( !m_playlistItem->itemBelow() ) return;
188 // storeTags();
190 // m_playlistItem = static_cast<PlaylistItem *>( m_playlistItem->itemBelow() );
192 // loadTags( m_playlistItem->url() );
193 // }
194 // else
195 // {
196 storeTags( *m_currentURL );
198 KUrl::List::iterator next = m_currentURL;
199 ++next;
200 if( next != m_urlList.end() )
201 ++m_currentURL;
202 loadTags( *m_currentURL );
203 enableItems();
204 // }
205 readTags();
208 inline void
209 TagDialog::perTrack()
211 m_perTrack = !m_perTrack;
212 if( m_perTrack )
214 // just switched to per track mode
215 applyToAllTracks();
216 setSingleTrackMode();
217 loadTags( *m_currentURL );
218 readTags();
220 else
222 storeTags( *m_currentURL );
223 setMultipleTracksMode();
224 readMultipleTracks();
227 enableItems();
231 void
232 TagDialog::enableItems()
234 checkBox_perTrack->setChecked( m_perTrack );
235 pushButton_previous->setEnabled( m_perTrack && m_currentURL != m_urlList.begin() );
236 KUrl::List::ConstIterator next = m_currentURL;
237 ++next;
238 pushButton_next->setEnabled( m_perTrack && next != m_urlList.end());
239 if( m_urlList.count() == 1 )
241 checkBox_perTrack->setEnabled( false );
243 else
245 checkBox_perTrack->setEnabled( true );
250 inline void
251 TagDialog::checkModified() //SLOT
253 pushButton_ok->setEnabled( hasChanged() || storedTags.count() > 0 || storedScores.count() > 0
254 || storedLyrics.count() > 0 || storedRatings.count() > 0 || newLabels.count() > 0 );
257 void
258 TagDialog::loadCover( const QString &artist, const QString &album )
260 if ( m_bundle.artist() != artist || m_bundle.album()!=album )
261 return;
263 // draw the album cover on the dialog
264 QString cover = CollectionDB::instance()->albumImage( m_bundle );
266 if( m_currentCover != cover )
268 pixmap_cover->setPixmap( QPixmap( cover, "PNG" ) );
269 m_currentCover = cover;
271 pixmap_cover->setInformation( m_bundle.artist(), m_bundle.album() );
272 const int s = AmarokConfig::coverPreviewSize();
273 pixmap_cover->setMinimumSize( s, s );
274 pixmap_cover->setMaximumSize( s, s );
278 void
279 TagDialog::setFileNameSchemes() //SLOT
281 KDialog *kDialog = new KDialog(this);
282 Ui::TagGuesserConfigDialog* dialog = new Ui::TagGuesserConfigDialog();
283 dialog->setupUi(kDialog);
284 kDialog->exec();
285 delete dialog;
289 void
290 TagDialog::guessFromFilename() //SLOT
292 int cur = 0;
294 TagGuesser guesser( m_bundle.url().path() );
295 if( !guesser.title().isNull() )
296 kLineEdit_title->setText( guesser.title() );
298 if( !guesser.artist().isNull() )
300 cur = kComboBox_artist->currentIndex();
301 kComboBox_artist->setItemText( cur, guesser.artist() );
304 if( !guesser.album().isNull() )
306 cur = kComboBox_album->currentIndex();
307 kComboBox_album->setItemText( cur, guesser.album() );
310 if( !guesser.track().isNull() )
311 qSpinBox_track->setValue( guesser.track().toInt() );
312 if( !guesser.comment().isNull() )
313 kTextEdit_comment->setText( guesser.comment() );
314 if( !guesser.year().isNull() )
315 qSpinBox_year->setValue( guesser.year().toInt() );
317 if( !guesser.composer().isNull() )
319 cur = kComboBox_composer->currentIndex();
320 kComboBox_composer->setItemText( cur, guesser.composer() );
323 if( !guesser.genre().isNull() )
325 cur = kComboBox_genre->currentIndex();
326 kComboBox_genre->setItemText( cur, guesser.genre() );
330 void
331 TagDialog::musicbrainzQuery() //SLOT
333 #if HAVE_TUNEPIMP
334 kDebug() ;
336 m_mbTrack = m_bundle.url();
337 KTRMLookup* ktrm = new KTRMLookup( m_mbTrack.path(), true );
338 connect( ktrm, SIGNAL( sigResult( KTRMResultList, QString ) ), SLOT( queryDone( KTRMResultList, QString ) ) );
339 connect( pushButton_cancel, SIGNAL( clicked() ), ktrm, SLOT( deleteLater() ) );
341 pushButton_musicbrainz->setEnabled( false );
342 pushButton_musicbrainz->setText( i18n( "Generating audio fingerprint..." ) );
343 QApplication::setOverrideCursor( Qt::BusyCursor );
344 #endif
347 void
348 TagDialog::queryDone( KTRMResultList results, QString error ) //SLOT
350 #if HAVE_TUNEPIMP
352 if ( !error.isEmpty() ) {
353 KMessageBox::sorry( this, i18n( "Tunepimp (MusicBrainz tagging library) returned the following error: \"%1\".", error) );
355 else {
356 if ( !results.isEmpty() )
358 TrackPickerDialog* t = new TrackPickerDialog( m_mbTrack.fileName(), results, this );
359 t->show();
360 connect( t, SIGNAL( finished() ), SLOT( resetMusicbrainz() ) ); // clear m_mbTrack
362 else {
363 KMessageBox::sorry( this, i18n( "The track was not found in the MusicBrainz database." ) );
364 resetMusicbrainz(); // clear m_mbTrack
368 QApplication::restoreOverrideCursor();
369 pushButton_musicbrainz->setEnabled( true );
370 pushButton_musicbrainz->setText( m_buttonMbText );
371 #else
372 Q_UNUSED(results);
373 Q_UNUSED(error);
374 #endif
377 void
378 TagDialog::fillSelected( KTRMResult selected ) //SLOT
380 #if HAVE_TUNEPIMP
381 kDebug() ;
384 if ( m_bundle.url() == m_mbTrack ) {
385 if ( !selected.title().isEmpty() ) kLineEdit_title->setText( selected.title() );
386 if ( !selected.artist().isEmpty() ) kComboBox_artist->setCurrentText( selected.artist() );
387 if ( !selected.album().isEmpty() ) kComboBox_album->setCurrentText( selected.album() );
388 if ( selected.track() != 0 ) qSpinBox_track->setValue( selected.track() );
389 if ( selected.year() != 0 ) qSpinBox_year->setValue( selected.year() );
390 } else {
391 MetaBundle mb;
392 mb.setPath( m_mbTrack.path() );
393 if ( !selected.title().isEmpty() ) mb.setTitle( selected.title() );
394 if ( !selected.artist().isEmpty() ) mb.setArtist( selected.artist() );
395 if ( !selected.album().isEmpty() ) mb.setAlbum( selected.album() );
396 if ( selected.track() != 0 ) mb.setTrack( selected.track() );
397 if ( selected.year() != 0 ) mb.setYear( selected.year() );
399 storedTags.replace( m_mbTrack.path(), mb );
401 #else
402 Q_UNUSED(selected);
403 #endif
406 void TagDialog::resetMusicbrainz() //SLOT
408 #if HAVE_TUNEPIMP
409 m_mbTrack = "";
410 #endif
413 ////////////////////////////////////////////////////////////////////////////////
414 // PRIVATE
415 ////////////////////////////////////////////////////////////////////////////////
417 void TagDialog::init()
419 DEBUG_BLOCK
422 // delete itself when closing
423 setAttribute( Qt::WA_DeleteOnClose );
425 KConfigGroup config = Amarok::config( "TagDialog" );
427 kTabWidget->addTab( summaryTab, i18n( "Summary" ) );
428 kTabWidget->addTab( tagsTab, i18n( "Tags" ) );
429 kTabWidget->addTab( lyricsTab, i18n( "Lyrics" ) );
430 kTabWidget->addTab( statisticsTab, i18n( "Statistics" ) );
431 kTabWidget->addTab( labelsTab, i18n( "Labels" ) );
432 kTabWidget->setCurrentIndex( config.readEntry( "CurrentTab", 0 ) );
434 int items = kComboBox_artist->count();
435 const QStringList artists = CollectionDB::instance()->artistList();
436 kComboBox_artist->insertItems( items, artists );
437 kComboBox_artist->completionObject()->insertItems( artists );
438 kComboBox_artist->completionObject()->setIgnoreCase( true );
439 kComboBox_artist->setCompletionMode( KGlobalSettings::CompletionPopup );
441 items = kComboBox_album->count();
442 const QStringList albums = CollectionDB::instance()->albumList();
443 kComboBox_album->insertItems( items, albums );
444 kComboBox_album->completionObject()->insertItems( albums );
445 kComboBox_album->completionObject()->setIgnoreCase( true );
446 kComboBox_album->setCompletionMode( KGlobalSettings::CompletionPopup );
448 items = kComboBox_artist->count();
449 const QStringList composers = CollectionDB::instance()->composerList();
450 kComboBox_composer->insertItems( items, composers );
451 kComboBox_composer->completionObject()->insertItems( composers );
452 kComboBox_composer->completionObject()->setIgnoreCase( true );
453 kComboBox_composer->setCompletionMode( KGlobalSettings::CompletionPopup );
455 items = kComboBox_artist->count();
456 kComboBox_rating->insertItems( items, MetaBundle::ratingList() );
458 // const QStringList genres = MetaBundle::genreList();
459 items = kComboBox_artist->count();
460 const QStringList genres = CollectionDB::instance()->genreList();
461 kComboBox_genre->insertItems( items, genres );
462 kComboBox_genre->completionObject()->insertItems( genres );
463 kComboBox_genre->completionObject()->setIgnoreCase( true );
465 const QStringList labels = CollectionDB::instance()->labelList();
466 //TODO: figure out a way to add auto-completion support to kTestEdit_selectedLabels
468 m_labelCloud = new KHTMLPart( labels_favouriteLabelsFrame );
469 //m_labelCloud = new HTMLView( labels_favouriteLabelsFrame );
470 //m_labelCloud->view()->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored, false );
471 QSizePolicy policy(QSizePolicy::Ignored, QSizePolicy::Ignored);
472 m_labelCloud->view()->setSizePolicy(policy);
474 //m_labelCloud->view()->setVScrollBarMode( Q3ScrollView::AlwaysOff );
475 //m_labelCloud->view()->setHScrollBarMode( Q3ScrollView::AlwaysOff );
477 new QVBoxLayout( labels_favouriteLabelsFrame );
478 labels_favouriteLabelsFrame->layout()->addWidget( m_labelCloud->view() );
479 const QStringList favoriteLabels = CollectionDB::instance()->favoriteLabels();
480 QString html = generateHTML( favoriteLabels );
481 m_labelCloud->write( html );
482 connect( m_labelCloud->browserExtension(), SIGNAL( openUrlRequest( const KUrl &, const KParts::OpenUrlArguments&, const KParts::BrowserArguments& ) ),
483 this, SLOT( openUrlRequest( const KUrl & ) ) );
485 // looks better to have a blank label than 0, we can't do this in
486 // the UI file due to bug in Designer
487 qSpinBox_track->setSpecialValueText( " " );
488 qSpinBox_year->setSpecialValueText( " " );
489 qSpinBox_score->setSpecialValueText( " " );
490 qSpinBox_discNumber->setSpecialValueText( " " );
492 if( !AmarokConfig::useRatings() )
494 kComboBox_rating->hide();
495 ratingLabel->hide();
497 if( !AmarokConfig::useScores() )
499 qSpinBox_score->hide();
500 scoreLabel->hide();
503 //HACK due to deficiency in Qt that will be addressed in version 4
504 // QSpinBox doesn't emit valueChanged if you edit the value with
505 // the lineEdit until you change the keyboard focus
506 connect( qSpinBox_year, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
507 connect( qSpinBox_track, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
508 connect( qSpinBox_score, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
509 connect( qSpinBox_discNumber, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
511 // Connects for modification check
512 connect( kLineEdit_title, SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
513 connect( kComboBox_composer,SIGNAL(activated( int )), SLOT(checkModified()) );
514 connect( kComboBox_composer,SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
515 connect( kComboBox_artist, SIGNAL(activated( int )), SLOT(checkModified()) );
516 connect( kComboBox_artist, SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
517 connect( kComboBox_album, SIGNAL(activated( int )), SLOT(checkModified()) );
518 connect( kComboBox_album, SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
519 connect( kComboBox_genre, SIGNAL(activated( int )), SLOT(checkModified()) );
520 connect( kComboBox_genre, SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
521 connect( kComboBox_rating, SIGNAL(activated( int )), SLOT(checkModified()) );
522 connect( kComboBox_rating, SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
523 connect( qSpinBox_track, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
524 connect( qSpinBox_year, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
525 connect( qSpinBox_score, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
526 connect( kTextEdit_comment, SIGNAL(textChanged()), SLOT(checkModified()) );
527 connect( kTextEdit_lyrics, SIGNAL(textChanged()), SLOT(checkModified()) );
528 connect( kTextEdit_selectedLabels, SIGNAL(textChanged()), SLOT(checkModified()) );
530 // Remember original button text
531 m_buttonMbText = pushButton_musicbrainz->text();
533 connect( pushButton_cancel, SIGNAL(clicked()), SLOT(cancelPressed()) );
534 connect( pushButton_ok, SIGNAL(clicked()), SLOT(accept()) );
535 connect( pushButton_open, SIGNAL(clicked()), SLOT(openPressed()) );
536 connect( pushButton_previous, SIGNAL(clicked()), SLOT(previousTrack()) );
537 connect( pushButton_next, SIGNAL(clicked()), SLOT(nextTrack()) );
538 connect( checkBox_perTrack, SIGNAL(clicked()), SLOT(perTrack()) );
540 // set an icon for the open-in-konqui button
541 pushButton_open->setIcon( KIcon( Amarok::icon( "files" ) ) );
543 //Update lyrics on Context Browser
544 // connect( this, SIGNAL(lyricsChanged( const QString& )), ContextBrowser::instance(), SLOT( lyricsChanged( const QString& ) ) );
546 //Update cover
547 connect( CollectionDB::instance(), SIGNAL( coverFetched( const QString&, const QString& ) ),
548 this, SLOT( loadCover( const QString&, const QString& ) ) );
549 connect( CollectionDB::instance(), SIGNAL( coverChanged( const QString&, const QString& ) ),
550 this, SLOT( loadCover( const QString&, const QString& ) ) );
554 #if HAVE_TUNEPIMP
555 connect( pushButton_musicbrainz, SIGNAL(clicked()), SLOT(musicbrainzQuery()) );
556 #else
557 pushButton_musicbrainz->setToolTip( i18n("Please install MusicBrainz to enable this functionality") );
558 #endif
560 connect( pushButton_guessTags, SIGNAL(clicked()), SLOT( guessFromFilename() ) );
561 connect( pushButton_setFilenameSchemes, SIGNAL(clicked()), SLOT( setFileNameSchemes() ) );
563 if( m_tracks.count() ) { //editing multiple tracks
564 m_perTrack = false;
565 setMultipleTracksMode();
566 readMultipleTracks();
568 checkBox_perTrack->setChecked( m_perTrack );
569 if( m_urlList.count() == 1 )
571 checkBox_perTrack->setEnabled( false );
572 pushButton_previous->setEnabled( false );
573 pushButton_next->setEnabled( false );
575 else
577 checkBox_perTrack->setEnabled( true );
578 pushButton_previous->setEnabled( m_perTrack );
579 pushButton_next->setEnabled( m_perTrack );
582 else
584 m_perTrack = true;
585 checkBox_perTrack->hide();
587 if( !m_playlistItem ) {
588 //We have already loaded the metadata (from the file) in the constructor
589 pushButton_previous->hide();
590 pushButton_next->hide();
592 else
594 //PORT 2.0
595 //Reload the metadata from the file, to be sure it's accurate
596 // loadTags( m_playlistItem->url() );
599 loadLyrics( m_bundle.url() );
600 loadLabels( m_bundle.url() );
601 readTags();
605 // make it as small as possible
606 resize( sizeHint().width(), minimumSize().height() );
611 inline const QString TagDialog::unknownSafe( QString s ) {
612 return ( s.isNull() || s.isEmpty() || s == "?" || s == "-" )
613 ? i18n ( "Unknown" )
614 : s;
617 const QStringList TagDialog::statisticsData() {
619 QStringList data, values;
620 const uint artist_id = CollectionDB::instance()->artistID( m_bundle.artist() );
621 const uint album_id = CollectionDB::instance()->albumID ( m_bundle.album() );
623 QueryBuilder qb;
625 if ( !m_bundle.artist().isEmpty() ) {
626 // tracks by this artist
627 qb.clear();
628 qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabSong, QueryBuilder::valTitle );
629 qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, QString::number( artist_id ) );
630 values = qb.run();
631 data += i18n( "Tracks by this Artist" );
632 data += values[0];
635 // albums by this artist
636 qb.clear();
637 qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabAlbum, QueryBuilder::valID );
638 qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, QString::number( artist_id ) );
639 qb.groupBy( QueryBuilder::tabSong, QueryBuilder::valAlbumID );
640 qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) );
641 qb.setOptions( QueryBuilder::optNoCompilations );
642 values = qb.run();
643 data += i18n( "Albums by this Artist" );
644 data += QString::number( values.count() );
647 // Favorite track by this artist
648 qb.clear();
649 qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
650 qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore );
651 qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, QString::number( artist_id ) );
652 qb.sortByFavorite();
653 qb.setLimit( 0, 1 );
654 values = qb.run();
655 data += i18n( "Favorite by this Artist" );
656 data += values.isEmpty() ? QString() : values[0];
658 if ( !m_bundle.album().isEmpty() ) {
659 // Favorite track on this album
660 qb.clear();
661 qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
662 qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore );
663 qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, QString::number( album_id ) );
664 qb.sortByFavorite();
665 qb.setLimit( 0, 1 );
666 values = qb.run();
667 data += i18n( "Favorite on this Album" );
668 data += values.isEmpty() ? QString() : values[0];
671 // Related Artists
672 const QString sArtists = CollectionDB::instance()->similarArtists( m_bundle.artist(), 4 ).join(", ");
673 if ( !sArtists.isEmpty() ) {
674 data += i18n( "Related Artists" );
675 data += sArtists;
678 return data;
681 void TagDialog::readTags()
683 bool local = m_currentTrack->playableUrl().isLocalFile();
685 setWindowTitle( KDialog::makeStandardCaption( i18n("Track Information: %1 by %2",
686 m_currentTrack->name(), m_currentTrack->artist() ? m_currentTrack->artist()->name() : QString() ) ) );
688 QString niceTitle;
689 if ( m_currentTrack->album()->name().isEmpty() ) {
690 if( !m_currentTrack->name().isEmpty() ) {
691 if( !m_currentTrack->artist()->name().isEmpty() )
692 niceTitle = i18n( "<b>%1</b> by <b>%2</b>", m_currentTrack->name(), m_currentTrack->artist()->name() );
693 else
694 niceTitle = QString( "<b>%1</b>").arg( m_currentTrack->name() );
696 else niceTitle = m_currentTrack->prettyName();
698 else {
699 niceTitle = i18n( "<b>%1</b> by <b>%2</b> on <b>%3</b>" ,
700 m_currentTrack->name(), m_currentTrack->artist()->name(), m_currentTrack->album()->name() );
702 trackArtistAlbumLabel->setText( niceTitle );
703 trackArtistAlbumLabel2->setText( niceTitle );
705 kLineEdit_title->setText( m_currentTrack->name() );
706 kComboBox_artist->setItemText( kComboBox_artist->currentIndex(), m_currentTrack->artist()->name() );
707 kComboBox_album->setItemText( kComboBox_album->currentIndex(), m_currentTrack->album()->name() );
708 kComboBox_genre->setItemText( kComboBox_genre->currentIndex(), m_currentTrack->genre()->name() );
709 kComboBox_rating->setCurrentIndex( m_currentTrack->rating() ? m_currentTrack->rating() - 1 : 0 );
710 qSpinBox_track->setValue( m_currentTrack->trackNumber() );
711 kComboBox_composer->setItemText( kComboBox_composer->currentIndex(), m_currentTrack->composer()->name() );
712 qSpinBox_year->setValue( m_currentTrack->year()->name().toInt() );
713 qSpinBox_score->setValue( static_cast<int>(m_currentTrack->score()) );
714 qSpinBox_discNumber->setValue( m_currentTrack->discNumber() );
715 kTextEdit_comment->setText( m_currentTrack->comment() );
717 //TODO Port 2.0
718 //bool extended = m_bundle.hasExtendedMetaInformation();
719 //qSpinBox_discNumber->setEnabled( extended );
720 //kComboBox_composer->setEnabled( extended );
723 QString summaryText, statisticsText;
724 const QString body2cols = "<tr><td><nobr>%1</nobr></td><td><b>%2</b></td></tr>";
725 const QString body1col = "<tr><td colspan=2>%1</td></td></tr>";
726 const QString emptyLine = "<tr><td colspan=2></td></tr>";
728 summaryText = "<table width=100%><tr><td width=50%><table>";
729 summaryText += body2cols.arg( i18n("Length:"), unknownSafe( m_bundle.prettyLength() ) );
730 summaryText += body2cols.arg( i18n("Bitrate:"), unknownSafe( m_bundle.prettyBitrate() ) );
731 summaryText += body2cols.arg( i18n("Samplerate:"), unknownSafe( m_bundle.prettySampleRate() ) );
732 summaryText += body2cols.arg( i18n("Size:"), unknownSafe( m_bundle.prettyFilesize() ) );
733 summaryText += body2cols.arg( i18n("Format:"), unknownSafe( m_currentTrack->type() ) );
735 summaryText += "</table></td><td width=50%><table>";
736 if( AmarokConfig::useScores() )
737 summaryText += body2cols.arg( i18n("Score:"), QString::number( static_cast<int>( m_currentTrack->score() ) ) );
738 if( AmarokConfig::useRatings() )
739 summaryText += body2cols.arg( i18n("Rating:"), m_bundle.prettyRating() );
741 summaryText += body2cols.arg( i18n("Playcount:"), QString::number( m_currentTrack->playCount() ) );
742 QDate firstPlayed = QDateTime::fromTime_t( m_currentTrack->firstPlayed() ).date();
743 QDate lastPlayed = QDateTime::fromTime_t( m_currentTrack->lastPlayed() ).date();
744 summaryText += body2cols.arg( i18n("First Played:"),
745 m_currentTrack->playCount() ? KGlobal::locale()->formatDate( firstPlayed, KLocale::ShortDate ) : i18n("Never") );
746 summaryText += body2cols.arg( i18nc("a single item (singular)", "Last Played:"),
747 m_currentTrack->playCount() ? KGlobal::locale()->formatDate( lastPlayed, KLocale::ShortDate ) : i18n("Never") );
749 summaryText += "</table></td></tr></table>";
750 summaryLabel->setText( summaryText );
752 statisticsText = "<table>";
754 QStringList sData = statisticsData();
755 for ( int i = 0; i < sData.count(); i+=2 ) {
756 statisticsText += body2cols.arg( sData[i], sData[i+1] );
759 statisticsText += "</table>";
761 statisticsLabel->setText( statisticsText );
763 kLineEdit_location->setText( local ? m_currentTrack->playableUrl().path() : m_currentTrack->playableUrl().url() );
765 //lyrics
766 kTextEdit_lyrics->setText( m_lyrics );
768 pixmap_cover->setPixmap( m_currentTrack->album()->image( AmarokConfig::coverPreviewSize(), false ) );
769 pixmap_cover->setInformation( m_currentTrack->artist()->name(), m_currentTrack->album()->name() );
770 const int s = AmarokConfig::coverPreviewSize();
771 pixmap_cover->setMinimumSize( s, s );
772 pixmap_cover->setMaximumSize( s, s );
775 // enable only for local files
776 kLineEdit_title->setReadOnly( !local );
777 kComboBox_artist->setEnabled( local );
778 kComboBox_album->setEnabled( local );
779 kComboBox_genre->setEnabled( local );
780 kComboBox_rating->setEnabled( local );
781 qSpinBox_track->setEnabled( local );
782 qSpinBox_year->setEnabled( local );
783 qSpinBox_score->setEnabled( local );
784 kTextEdit_comment->setEnabled( local );
785 kTextEdit_selectedLabels->setEnabled( local );
786 m_labelCloud->view()->setEnabled( local );
788 if( local )
790 pushButton_musicbrainz->show();
791 pushButton_guessTags->show();
792 pushButton_setFilenameSchemes->show();
794 else
796 pushButton_musicbrainz->hide();
797 pushButton_guessTags->hide();
798 pushButton_setFilenameSchemes->hide();
801 // If it's a local file, write the directory to m_path, else disable the "open in konqui" button
802 if ( local )
803 m_path = m_currentTrack->playableUrl().directory();
804 else
805 pushButton_open->setEnabled( false );
807 pushButton_ok->setEnabled( storedTags.count() > 0 || storedScores.count() > 0
808 || storedLyrics.count() > 0 || storedRatings.count() > 0
809 || newLabels.count() > 0 );
811 #if HAVE_TUNEPIMP
812 // Don't enable button if a query is in progress already (or if the file isn't local)
813 pushButton_musicbrainz->setEnabled( m_bundle.url().isLocalFile() && m_mbTrack.isEmpty() );
814 #else
815 pushButton_musicbrainz->setEnabled( false );
816 #endif
818 //PORT 2.0
819 // if( m_playlistItem ) {
820 // pushButton_previous->setEnabled( m_playlistItem->itemAbove() );
821 // pushButton_next->setEnabled( m_playlistItem->itemBelow() );
822 // }
826 void
827 TagDialog::setMultipleTracksMode()
830 kTabWidget->setTabEnabled( kTabWidget->indexOf(summaryTab), false );
831 kTabWidget->setTabEnabled( kTabWidget->indexOf(lyricsTab), false );
833 kComboBox_artist->setItemText( kComboBox_artist->currentIndex(), "" );
834 kComboBox_album->setItemText( kComboBox_album->currentIndex(), "" );
835 kComboBox_genre->setItemText( kComboBox_genre->currentIndex(), "" );
836 kComboBox_composer->setItemText( kComboBox_composer->currentIndex(), "" );
837 kLineEdit_title->setText( "" );
838 kTextEdit_comment->setText( "" );
839 qSpinBox_track->setValue( qSpinBox_track->minimum() );
840 qSpinBox_discNumber->setValue( qSpinBox_discNumber->minimum() );
841 qSpinBox_year->setValue( qSpinBox_year->minimum() );
843 qSpinBox_score->setValue( qSpinBox_score->minimum() );
844 kComboBox_rating->setCurrentItem( 0 );
846 kLineEdit_title->setEnabled( false );
847 qSpinBox_track->setEnabled( false );
849 pushButton_musicbrainz->hide();
850 pushButton_guessTags->hide();
851 pushButton_setFilenameSchemes->hide();
853 locationLabel->hide();
854 kLineEdit_location->hide();
855 pushButton_open->hide();
856 pixmap_cover->hide();
859 void
860 TagDialog::setSingleTrackMode()
863 kTabWidget->setTabEnabled( kTabWidget->indexOf(summaryTab), true );
864 kTabWidget->setTabEnabled( kTabWidget->indexOf(lyricsTab), true );
866 kLineEdit_title->setEnabled( true );
867 qSpinBox_track->setEnabled( true );
869 pushButton_musicbrainz->show();
870 pushButton_guessTags->show();
871 pushButton_setFilenameSchemes->show();
873 locationLabel->show();
874 kLineEdit_location->show();
875 pushButton_open->show();
876 pixmap_cover->show();
880 void
881 TagDialog::readMultipleTracks()
884 setWindowTitle( KDialog::makeStandardCaption( i18np("1 Track", "Information for %1 Tracks", m_urlList.count()) ) );
886 //Check which fields are the same for all selected tracks
887 const KUrl::List::ConstIterator end = m_urlList.end();
888 KUrl::List::ConstIterator it = m_urlList.begin();
890 m_bundle = MetaBundle();
892 MetaBundle first = bundleForURL( *it );
894 bool artist=true, album=true, genre=true, comment=true, year=true,
895 score=true, rating=true, composer=true, discNumber=true;
896 int songCount=0, ratingCount=0, ratingSum=0, scoreCount=0;
897 float scoreSum = 0.f;
898 for ( ; it != end; ++it ) {
899 MetaBundle mb = bundleForURL( *it );
900 songCount++;
901 if ( mb.rating() ) {
902 ratingCount++;
903 ratingSum+=mb.rating();
905 if ( mb.score() > 0.f ) {
906 scoreCount++;
907 scoreSum+=mb.score();
910 if( !mb.url().isLocalFile() ) {
911 // If we have a non local file, don't even lose more time comparing
912 artist = album = genre = comment = year = false;
913 score = rating = composer = discNumber = false;
914 continue;
916 if ( artist && mb.artist()!=first.artist() )
917 artist=false;
918 if ( album && mb.album()!=first.album() )
919 album=false;
920 if ( genre && mb.genre()!=first.genre() )
921 genre=false;
922 if ( comment && mb.comment()!=first.comment() )
923 comment=false;
924 if ( year && mb.year()!=first.year() )
925 year=false;
926 if ( composer && mb.composer()!=first.composer() )
927 composer=false;
928 if ( discNumber && mb.discNumber()!=first.discNumber() )
929 discNumber=false;
930 if ( score && mb.score()!=first.score() )
931 score = false;
932 if ( rating && mb.rating()!=first.rating() )
933 rating = false;
935 // Set them in the dialog and in m_bundle ( so we don't break hasChanged() )
936 int cur_item;
937 if (artist) {
938 cur_item = kComboBox_artist->currentIndex();
939 m_bundle.setArtist( first.artist() );
940 kComboBox_artist->setItemText( cur_item, first.artist() );
942 if (album) {
943 cur_item = kComboBox_album->currentIndex();
944 m_bundle.setAlbum( first.album() );
945 kComboBox_album->setItemText( cur_item, first.album() );
947 if (genre) {
948 cur_item = kComboBox_genre->currentIndex();
949 m_bundle.setGenre( first.genre() );
950 kComboBox_genre->setItemText( cur_item, first.genre() );
952 if (comment) {
953 m_bundle.setComment( first.comment() );
954 kTextEdit_comment->setText( first.comment() );
956 if (composer) {
957 cur_item = kComboBox_composer->currentIndex();
958 m_bundle.setComposer( first.composer() );
959 kComboBox_composer->setItemText( cur_item, first.composer() );
961 if (year) {
962 m_bundle.setYear( first.year() );
963 qSpinBox_year->setValue( first.year() );
965 if (discNumber) {
966 m_bundle.setDiscNumber( first.discNumber() );
967 qSpinBox_discNumber->setValue( first.discNumber() );
969 if (score) {
970 m_bundle.setScore( first.score() );
971 qSpinBox_score->setValue( static_cast<int>( first.score() ) );
973 if (rating) {
974 m_bundle.setRating( first.rating() );
975 kComboBox_rating->setCurrentIndex( first.rating() ? first.rating() - 1 : 0 );
978 m_currentURL = m_urlList.begin();
980 trackArtistAlbumLabel2->setText( i18np( "Editing 1 file", "Editing %1 files", songCount ) );
982 const QString body = "<tr><td><nobr>%1:</nobr></td><td><b>%2</b></td></tr>";
983 QString statisticsText = "<table>";
985 if( AmarokConfig::useRatings() ) {
986 statisticsText += body.arg( i18n( "Rated Songs:" ) , QString::number( ratingCount ) );
987 if ( ratingCount )
988 statisticsText += body.arg( i18n( "Average Rating:" ) , QString::number( (float)ratingSum / (float)ratingCount/2.0, 'f', 1 ) );
991 if( AmarokConfig::useRatings() ) {
992 statisticsText += body.arg( i18n( "Scored Songs:" ) , QString::number( scoreCount ) );
993 if ( scoreCount )
994 statisticsText += body.arg( i18n( "Average Score:" ) , QString::number( scoreSum / scoreCount, 'f', 1 ) );
998 statisticsText += "</table>";
1000 statisticsLabel->setText( statisticsText );
1002 QStringList commonLabels = getCommonLabels();
1003 QString text;
1004 oldForeach ( commonLabels )
1006 if ( !text.isEmpty() )
1007 text.append( ", " );
1008 text.append( *it );
1010 kTextEdit_selectedLabels->setText( text );
1011 m_commaSeparatedLabels = text;
1013 // This will reset a wrongly enabled Ok button
1014 checkModified();
1017 QStringList
1018 TagDialog::getCommonLabels()
1020 DEBUG_BLOCK
1021 QMap<QString, int> counterMap;
1022 const KUrl::List::ConstIterator end = m_urlList.end();
1023 KUrl::List::ConstIterator iter = m_urlList.begin();
1024 for(; iter != end; ++iter )
1026 QStringList labels = labelsForURL( *iter );
1027 oldForeach( labels )
1029 if ( counterMap.contains( *it ) )
1030 counterMap[ *it ] = counterMap[ *it ] +1;
1031 else
1032 counterMap[ *it ] = 1;
1035 int n = m_urlList.count();
1036 QStringList result;
1037 QMap<QString, int>::ConstIterator counterEnd( counterMap.end() );
1038 for(QMap<QString, int>::ConstIterator it = counterMap.begin(); it != counterEnd; ++it )
1040 if ( it.value() == n )
1041 result.append( it.key() );
1043 return result;
1046 inline bool
1047 equalString( const QString &a, const QString &b )
1049 return (a.isEmpty() && b.isEmpty()) ? true : a == b;
1052 bool
1053 TagDialog::hasChanged()
1055 return changes();
1059 TagDialog::changes()
1061 int result=TagDialog::NOCHANGE;
1062 bool modified = false;
1063 modified |= !equalString( kComboBox_artist->lineEdit()->text(), m_bundle.artist() );
1064 modified |= !equalString( kComboBox_album->lineEdit()->text(), m_bundle.album() );
1065 modified |= !equalString( kComboBox_genre->lineEdit()->text(), m_bundle.genre() );
1066 modified |= qSpinBox_year->value() != m_bundle.year();
1067 modified |= qSpinBox_discNumber->value() != m_bundle.discNumber();
1068 modified |= !equalString( kComboBox_composer->lineEdit()->text(), m_bundle.composer() );
1070 modified |= !equalString( kTextEdit_comment->toPlainText(), m_bundle.comment() );
1072 if (!m_urlList.count() || m_perTrack) { //ignore these on MultipleTracksMode
1073 modified |= !equalString( kLineEdit_title->text(), m_bundle.title() );
1074 modified |= qSpinBox_track->value() != m_bundle.track();
1076 if (modified)
1077 result |= TagDialog::TAGSCHANGED;
1079 if (qSpinBox_score->value() != m_bundle.score())
1080 result |= TagDialog::SCORECHANGED;
1081 if (kComboBox_rating->currentIndex() != ( m_bundle.rating() ? m_bundle.rating() - 1 : 0 ) )
1082 result |= TagDialog::RATINGCHANGED;
1084 if (!m_urlList.count() || m_perTrack) { //ignore these on MultipleTracksMode
1085 if ( !equalString( kTextEdit_lyrics->toPlainText(), m_lyrics ) )
1086 result |= TagDialog::LYRICSCHANGED;
1089 if ( !equalString( kTextEdit_selectedLabels->toPlainText(), m_commaSeparatedLabels ) )
1090 result |= TagDialog::LABELSCHANGED;
1092 return result;
1095 void
1096 TagDialog::storeTags()
1098 storeTags( m_bundle.url() );
1101 void
1102 TagDialog::storeTags( const KUrl &kurl )
1104 int result = changes();
1105 QString url = kurl.path();
1106 if( result & TagDialog::TAGSCHANGED ) {
1107 MetaBundle mb( m_bundle );
1109 mb.setTitle( kLineEdit_title->text() );
1110 mb.setComposer( kComboBox_composer->currentText() );
1111 mb.setArtist( kComboBox_artist->currentText() );
1112 mb.setAlbum( kComboBox_album->currentText() );
1113 mb.setComment( kTextEdit_comment->toPlainText() );
1114 mb.setGenre( kComboBox_genre->currentText() );
1115 mb.setTrack( qSpinBox_track->value() );
1116 mb.setYear( qSpinBox_year->value() );
1117 mb.setDiscNumber( qSpinBox_discNumber->value() );
1118 mb.setLength( m_bundle.length() );
1119 mb.setBitrate( m_bundle.bitrate() );
1120 mb.setSampleRate( m_bundle.sampleRate() );
1121 storedTags.remove( url );
1122 storedTags.insert( url, mb );
1124 if( result & TagDialog::SCORECHANGED ) {
1125 storedScores.remove( url );
1126 storedScores.insert( url, qSpinBox_score->value() );
1129 if( result & TagDialog::RATINGCHANGED ) {
1130 storedRatings.remove( url );
1131 storedRatings.insert( url, kComboBox_rating->currentIndex() ? kComboBox_rating->currentIndex() + 1 : 0 );
1134 if( result & TagDialog::LYRICSCHANGED ) {
1135 if ( kTextEdit_lyrics->toPlainText().isEmpty() ) {
1136 storedLyrics.remove( url );
1137 storedLyrics.insert( url, QString() );
1139 else {
1140 QDomDocument doc;
1141 QDomElement e = doc.createElement( "lyrics" );
1142 e.setAttribute( "artist", kComboBox_artist->currentText() );
1143 e.setAttribute( "title", kLineEdit_title->text() );
1144 QDomText t = doc.createTextNode( kTextEdit_lyrics->toPlainText() );
1145 e.appendChild( t );
1146 doc.appendChild( e );
1147 storedLyrics.remove( url );
1148 storedLyrics.insert( url, doc.toString() );
1151 if( result & TagDialog::LABELSCHANGED ) {
1152 generateDeltaForLabelList( labelListFromText( kTextEdit_selectedLabels->toPlainText() ) );
1153 QStringList tmpLabels;
1154 if ( newLabels.find( url ) != newLabels.end() )
1155 tmpLabels = newLabels[ url ];
1156 else
1157 tmpLabels = originalLabels[ url ];
1158 //apply delta
1159 oldForeach( m_removedLabels )
1161 tmpLabels.removeAt( tmpLabels.indexOf(*it) );
1163 oldForeach( m_addedLabels )
1165 // this just feels dirty...
1166 if( tmpLabels.indexOf( *it ) == tmpLabels.indexOf( *tmpLabels.end() ) )
1167 tmpLabels.append( *it );
1169 newLabels.remove( url );
1170 newLabels.insert( url, tmpLabels );
1174 void
1175 TagDialog::storeTags( const KUrl &url, int changes, const MetaBundle &mb )
1177 if ( changes & TagDialog::TAGSCHANGED ) {
1178 storedTags.remove( url.path() );
1179 storedTags.insert( url.path(), mb );
1181 if ( changes & TagDialog::SCORECHANGED ) {
1182 storedScores.remove( url.path() );
1183 storedScores.insert( url.path(), mb.score() );
1185 if ( changes & TagDialog::RATINGCHANGED ) {
1186 storedRatings.remove( url.path() );
1187 storedRatings.insert( url.path(), mb.rating() );
1191 void
1192 TagDialog::storeLabels( const KUrl &url, const QStringList &labels )
1194 newLabels.remove( url.path() );
1195 newLabels.insert( url.path(), labels );
1199 void
1200 TagDialog::loadTags( const KUrl &url )
1202 m_bundle = bundleForURL( url );
1203 loadLyrics( url );
1204 loadLabels( url );
1207 void
1208 TagDialog::loadLyrics( const KUrl &url )
1210 QString xml = lyricsForURL(url.path() );
1212 QDomDocument doc;
1213 if( doc.setContent( xml ) )
1214 m_lyrics = doc.documentElement().text();
1215 else
1216 m_lyrics.clear();
1219 void
1220 TagDialog::loadLabels( const KUrl &url )
1222 DEBUG_BLOCK
1223 m_labels = labelsForURL( url );
1224 originalLabels[ url.path() ] = m_labels;
1225 QString text;
1226 oldForeach( m_labels )
1228 if ( !text.isEmpty() )
1229 text.append( ", " );
1230 text.append( *it );
1232 kTextEdit_selectedLabels->setText( text );
1233 m_commaSeparatedLabels = text;
1236 MetaBundle
1237 TagDialog::bundleForURL( const KUrl &url )
1239 if( storedTags.find( url.path() ) != storedTags.end() )
1240 return storedTags[ url.path() ];
1242 return MetaBundle( url, url.isLocalFile() );
1245 float
1246 TagDialog::scoreForURL( const KUrl &url )
1248 if( storedScores.find( url.path() ) != storedScores.end() )
1249 return storedScores[ url.path() ];
1251 return CollectionDB::instance()->getSongPercentage( url.path() );
1255 TagDialog::ratingForURL( const KUrl &url )
1257 if( storedRatings.find( url.path() ) != storedRatings.end() )
1258 return storedRatings[ url.path() ];
1260 return CollectionDB::instance()->getSongRating( url.path() );
1263 QString
1264 TagDialog::lyricsForURL( const KUrl &url )
1266 if( storedLyrics.find( url.path() ) != storedLyrics.end() )
1267 return storedLyrics[ url.path() ];
1269 return CollectionDB::instance()->getLyrics( url.path() );
1272 QStringList
1273 TagDialog::labelsForURL( const KUrl &url )
1275 if( newLabels.find( url.path() ) != newLabels.end() )
1276 return newLabels[ url.path() ];
1277 if( originalLabels.find( url.path() ) != originalLabels.end() )
1278 return originalLabels[ url.path() ];
1279 QStringList tmp = CollectionDB::instance()->getLabels( url.path(), CollectionDB::typeUser );
1280 originalLabels[ url.path() ] = tmp;
1281 return tmp;
1284 void
1285 TagDialog::saveTags()
1287 if( !m_perTrack )
1289 applyToAllTracks();
1291 else
1293 storeTags();
1296 QMap<QString, float>::ConstIterator endScore( storedScores.end() );
1297 for(QMap<QString, float>::ConstIterator it = storedScores.begin(); it != endScore; ++it ) {
1298 CollectionDB::instance()->setSongPercentage( it.key(), it.value() );
1300 QMap<QString, int>::ConstIterator endRating( storedRatings.end() );
1301 for(QMap<QString, int>::ConstIterator it = storedRatings.begin(); it != endRating; ++it ) {
1302 CollectionDB::instance()->setSongRating( it.key(), it.value() );
1304 QMap<QString, QString>::ConstIterator endLyrics( storedLyrics.end() );
1305 for(QMap<QString, QString>::ConstIterator it = storedLyrics.begin(); it != endLyrics; ++it ) {
1306 CollectionDB::instance()->setLyrics( it.key(), it.value(),
1307 CollectionDB::instance()->uniqueIdFromUrl( KUrl( it.key() ) ) );
1308 emit lyricsChanged( it.key() );
1310 QMap<QString, QStringList>::ConstIterator endLabels( newLabels.end() );
1311 for(QMap<QString, QStringList>::ConstIterator it = newLabels.begin(); it != endLabels; ++it ) {
1312 CollectionDB::instance()->setLabels( it.key(), it.value(),
1313 CollectionDB::instance()->uniqueIdFromUrl( KUrl( it.key() ) ), CollectionDB::typeUser );
1315 CollectionDB::instance()->cleanLabels();
1317 ThreadManager::instance()->queueJob( new TagDialogWriter( storedTags ) );
1321 void
1322 TagDialog::applyToAllTracks()
1324 generateDeltaForLabelList( labelListFromText( kTextEdit_selectedLabels->toPlainText() ) );
1326 const KUrl::List::ConstIterator end = m_urlList.end();
1327 for ( KUrl::List::ConstIterator it = m_urlList.begin(); it != end; ++it ) {
1329 /* we have to update the values if they changed, so:
1330 1) !kLineEdit_field->text().isEmpty() && kLineEdit_field->text() != mb.field
1331 i.e.: The user wrote something on the field, and it's different from
1332 what we have in the tag.
1333 2) !m_bundle.field().isEmpty() && kLineEdit_field->text().isEmpty()
1334 i.e.: The user was shown some value for the field (it was the same
1335 for all selected tracks), and he deliberately emptied it.
1336 TODO: All this mess is because the dialog uses "" to represent what the user
1337 doesn't want to change, maybe we can think of something better?
1340 MetaBundle mb = bundleForURL( *it );
1342 int changed = 0;
1343 if( !kComboBox_artist->currentText().isEmpty() && kComboBox_artist->currentText() != mb.artist() ||
1344 kComboBox_artist->currentText().isEmpty() && !m_bundle.artist().isEmpty() ) {
1345 mb.setArtist( kComboBox_artist->currentText() );
1346 changed |= TagDialog::TAGSCHANGED;
1349 if( !kComboBox_album->currentText().isEmpty() && kComboBox_album->currentText() != mb.album() ||
1350 kComboBox_album->currentText().isEmpty() && !m_bundle.album().isEmpty() ) {
1351 mb.setAlbum( kComboBox_album->currentText() );
1352 changed |= TagDialog::TAGSCHANGED;
1354 if( !kComboBox_genre->currentText().isEmpty() && kComboBox_genre->currentText() != mb.genre() ||
1355 kComboBox_genre->currentText().isEmpty() && !m_bundle.genre().isEmpty() ) {
1356 mb.setGenre( kComboBox_genre->currentText() );
1357 changed |= TagDialog::TAGSCHANGED;
1359 if( !kTextEdit_comment->toPlainText().isEmpty() && kTextEdit_comment->toPlainText() != mb.comment() ||
1360 kTextEdit_comment->toPlainText().isEmpty() && !m_bundle.comment().isEmpty() ) {
1361 mb.setComment( kTextEdit_comment->toPlainText() );
1362 changed |= TagDialog::TAGSCHANGED;
1364 if( !kComboBox_composer->currentText().isEmpty() && kComboBox_composer->currentText() != mb.composer() ||
1365 kComboBox_composer->currentText().isEmpty() && !m_bundle.composer().isEmpty() ) {
1366 mb.setComposer( kComboBox_composer->currentText() );
1367 changed |= TagDialog::TAGSCHANGED;
1370 if( qSpinBox_year->value() && qSpinBox_year->value() != mb.year() ||
1371 !qSpinBox_year->value() && m_bundle.year() ) {
1372 mb.setYear( qSpinBox_year->value() );
1373 changed |= TagDialog::TAGSCHANGED;
1375 if( qSpinBox_discNumber->value() && qSpinBox_discNumber->value() != mb.discNumber() ||
1376 !qSpinBox_discNumber->value() && m_bundle.discNumber() ) {
1377 mb.setDiscNumber( qSpinBox_discNumber->value() );
1378 changed |= TagDialog::TAGSCHANGED;
1381 if( qSpinBox_score->value() && qSpinBox_score->value() != mb.score() ||
1382 !qSpinBox_score->value() && m_bundle.score() )
1384 mb.setScore( qSpinBox_score->value() );
1385 changed |= TagDialog::SCORECHANGED;
1388 if( kComboBox_rating->currentIndex() && kComboBox_rating->currentIndex() != m_bundle.rating() - 1 ||
1389 !kComboBox_rating->currentIndex() && m_bundle.rating() )
1391 mb.setRating( kComboBox_rating->currentIndex() ? kComboBox_rating->currentIndex() + 1 : 0 );
1392 changed |= TagDialog::RATINGCHANGED;
1394 storeTags( *it, changed, mb );
1396 QStringList tmpLabels = labelsForURL( *it );
1397 //apply delta
1398 for( QStringList::Iterator iter = m_removedLabels.begin(); iter != m_removedLabels.end(); ++iter )
1400 tmpLabels.erase( iter );
1402 for( QStringList::Iterator iter = m_addedLabels.begin(); iter != m_addedLabels.end(); ++iter )
1404 if( tmpLabels.indexOf( *iter ) == tmpLabels.indexOf( *tmpLabels.end() ) )
1405 tmpLabels.append( *iter );
1407 storeLabels( *it, tmpLabels );
1411 QStringList
1412 TagDialog::labelListFromText( const QString &text )
1414 QStringList tmp = text.split(',', QString::SkipEmptyParts);
1415 //insert each string into a map to remove duplicates
1416 QMap<QString, int> map;
1417 oldForeach( tmp )
1419 QString tmpString = (*it).trimmed();
1420 if ( !tmpString.isEmpty() ) {
1421 map.remove( tmpString );
1422 map.insert( tmpString, 0 );
1425 QStringList result;
1426 QMap<QString, int>::ConstIterator endMap( map.end() );
1427 for(QMap<QString, int>::ConstIterator it = map.begin(); it != endMap; ++it ) {
1428 result.append( it.key() );
1430 return result;
1433 void
1434 TagDialog::generateDeltaForLabelList( const QStringList &list )
1436 m_addedLabels.clear();
1437 m_removedLabels.clear();
1438 oldForeach( list )
1440 if ( !m_labels.contains( *it ) )
1441 m_addedLabels.append( *it );
1443 oldForeach( m_labels )
1445 if ( !list.contains( *it ) )
1446 m_removedLabels.append( *it );
1448 m_labels = list;
1451 QString
1452 TagDialog::generateHTML( const QStringList &labels )
1454 //the first column of each row is the label name, the second the number of assigned songs
1455 //loop through it to find the highest number of songs, can be removed if somebody figures out a better sql query
1456 QMap<QString, QPair<QString, int> > mapping;
1457 QStringList sortedLabels;
1458 int max = 1;
1459 oldForeach( labels )
1461 QString label = *it;
1462 sortedLabels << label.toLower();
1463 ++it;
1464 int value = ( *it ).toInt();
1465 if ( value > max )
1466 max = value;
1467 mapping[label.toLower()] = QPair<QString, int>( label, value );
1469 sortedLabels.sort();
1470 QString html = "<html><body>";
1471 oldForeach( sortedLabels )
1473 QString key = *it;
1474 //generate a number in the range 1..10 based on how much the label is used
1475 int labelUse = ( mapping[key].second * 10 ) / max;
1476 if ( labelUse == 0 )
1477 labelUse = 1;
1478 html.append( QString( "<span class='label size%1'><a href=\"label:%2\">%3</a></span> " )
1479 .arg( QString::number( labelUse ), mapping[key].first, mapping[key].first ) );
1481 html.append( "</html></body>" );
1482 debug() << "Dumping HTML for label cloud: " << html;
1483 return html;
1486 void
1487 TagDialog::openUrlRequest(const KUrl &url ) //SLOT
1489 DEBUG_BLOCK
1490 if ( url.protocol() == "label" )
1492 QString text = kTextEdit_selectedLabels->toPlainText();
1493 QStringList currentLabels = labelListFromText( text );
1494 if ( currentLabels.contains( url.path() ) )
1495 return;
1496 if ( !text.isEmpty() )
1497 text.append( ", " );
1498 text.append( url.path() );
1499 kTextEdit_selectedLabels->setText( text );
1503 bool
1504 TagDialog::writeTag( MetaBundle &mb, bool updateCB )
1506 QByteArray path = QFile::encodeName( mb.url().path() );
1507 if ( !TagLib::File::isWritable( path ) ) {
1508 Amarok::ContextStatusBar::instance()->longMessage( i18n(
1509 "The file %1 is not writable.", mb.url().fileName() ), KDE::StatusBar::Error );
1510 return false;
1513 //visual feedback
1514 QApplication::setOverrideCursor( Qt::WaitCursor );
1516 bool result = mb.save();
1517 mb.updateFilesize();
1519 if( result )
1520 //update the collection db
1521 CollectionDB::instance()->updateTags( mb.url().path(), mb, updateCB );
1523 QApplication::restoreOverrideCursor();
1525 return result;
1528 TagDialogWriter::TagDialogWriter( const QMap<QString, MetaBundle> tagsToChange )
1529 : ThreadManager::Job( "TagDialogWriter" ),
1530 m_successCount ( 0 ),
1531 m_failCount ( 0 )
1533 QApplication::setOverrideCursor( Qt::WaitCursor );
1534 QMap<QString, MetaBundle>::ConstIterator end = tagsToChange.end();
1535 for(QMap<QString, MetaBundle>::ConstIterator it = tagsToChange.begin(); it != end; ++it ) {
1536 MetaBundle mb = it.value();
1537 m_tags += mb;
1541 bool
1542 TagDialogWriter::doJob()
1544 for( int i = 0, size=m_tags.size(); i<size; ++i ) {
1545 QByteArray path = QFile::encodeName( m_tags[i].url().path() );
1546 if ( !TagLib::File::isWritable( path ) ) {
1547 Amarok::ContextStatusBar::instance()->longMessageThreadSafe( i18n(
1548 "The file %1 is not writable.", m_tags[i].url().fileName() ), KDE::StatusBar::Error );
1549 m_failed += true;
1550 continue;
1553 bool result = m_tags[i].save();
1554 m_tags[i].updateFilesize();
1556 if( result )
1557 m_successCount++;
1558 else {
1559 m_failCount++;
1560 m_failedURLs += m_tags[i].prettyUrl();
1562 m_failed += !result;
1564 return true;
1567 void
1568 TagDialogWriter::completeJob()
1570 for( int i = 0, size=m_tags.size(); i<size; ++i ) {
1571 if ( !m_failed[i] ) {
1572 CollectionDB::instance()->updateTags( m_tags[i].url().path(), m_tags[i], false /* don't update browsers*/ );
1573 // PORT 2.0 Playlist::instance()->updateMetaData( m_tags[i] );
1576 QApplication::restoreOverrideCursor();
1577 //PORT 2.0 if ( m_successCount )
1578 //PORT 2.0 CollectionView::instance()->databaseChanged();
1579 if ( m_failCount )
1580 Amarok::ContextStatusBar::instance()->longMessage( i18n(
1581 "Sorry, the tag for the following files could not be changed:\n", m_failedURLs.join( ";\n" ) ), KDE::StatusBar::Error );
1585 #include "tagdialog.moc"