Make playlist items use the full width available to them and adjust correctly when...
[amarok.git] / src / tagdialog.cpp
blob00612d2086b4d1fa08c479f24fee4a9df30fe0f1
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 "coverfetcher.h"
10 #include "debug.h"
11 #include "metabundle.h"
12 #include "playlist.h"
13 #include "playlistitem.h"
14 #include "querybuilder.h"
15 #include "statusbar.h" //for status messages
16 #include "tagguesser.h"
17 #include "trackpickerdialog.h"
18 #include "ui_tagguesserconfigdialog.h"
20 #include <KApplication>
21 #include <KComboBox>
22 #include <KCursor>
23 #include <KGlobal>
24 #include <KHTMLView>
25 #include <KIconLoader>
26 #include <KLineEdit>
27 #include <KMessageBox>
28 #include <KNumInput>
29 #include <KRun>
30 #include <KStandardDirs>
31 #include <KTabWidget>
32 #include <KTextEdit>
33 #include <KVBox>
35 #include <QCheckBox>
36 #include <qdom.h>
37 #include <QFile>
38 #include <QLabel>
39 #include <QLayout>
40 #include <QPair>
41 #include <QPushButton>
42 #include <QToolTip>
44 #include <tfile.h> //TagLib::File::isWritable
46 class TagDialogWriter : public ThreadManager::Job
48 public:
49 TagDialogWriter( const QMap<QString, MetaBundle> tagsToChange );
50 bool doJob();
51 void completeJob();
52 private:
53 QList<bool> m_failed;
54 QList<MetaBundle> m_tags;
55 bool m_updateView;
56 int m_successCount;
57 int m_failCount;
58 QStringList m_failedURLs;
61 TagDialog::TagDialog( const KUrl& url, QWidget* parent )
62 : TagDialogBase( parent )
63 , m_bundle( url, true )
64 , m_playlistItem( 0 )
65 , m_currentCover( 0 )
67 init();
71 TagDialog::TagDialog( const KUrl::List list, QWidget* parent )
72 : TagDialogBase( parent )
73 , m_bundle()
74 , m_playlistItem( 0 )
75 , m_urlList( list )
76 , m_currentCover( 0 )
78 init();
82 TagDialog::TagDialog( const MetaBundle& mb, PlaylistItem* item, QWidget* parent )
83 : TagDialogBase( parent )
84 , m_bundle( mb )
85 , m_playlistItem( item )
86 , m_currentCover( 0 )
88 init();
91 TagDialog::TagDialog( const Meta::TrackList &tracks, QWidget *parent )
92 :TagDialogBase( parent )
93 , m_currentCover()
94 , m_tracks( tracks )
95 , m_currentTrack( 0 )
97 init();
100 TagDialog::TagDialog( Meta::TrackPtr track, QWidget *parent )
101 :TagDialogBase( parent )
102 , m_currentCover()
103 , m_tracks()
104 , m_currentTrack( track )
106 m_tracks.append( track );
107 init();
111 TagDialog::~TagDialog()
113 DEBUG_BLOCK
115 Amarok::config( "TagDialog" ).writeEntry( "CurrentTab", kTabWidget->currentIndex() );
118 void
119 TagDialog::setTab( int id )
121 kTabWidget->setCurrentIndex( id );
125 ////////////////////////////////////////////////////////////////////////////////
126 // PRIVATE SLOTS
127 ////////////////////////////////////////////////////////////////////////////////
129 void
130 TagDialog::cancelPressed() //SLOT
132 QApplication::restoreOverrideCursor(); // restore the cursor before closing the dialog
133 reject();
137 void
138 TagDialog::accept() //SLOT
140 pushButton_ok->setEnabled( false ); //visual feedback
141 saveTags();
143 QDialog::accept();
147 inline void
148 TagDialog::openPressed() //SLOT
150 Amarok::invokeBrowser( m_path );
154 inline void
155 TagDialog::previousTrack()
157 if( m_playlistItem )
159 if( !m_playlistItem->itemAbove() ) return;
161 storeTags();
163 m_playlistItem = static_cast<PlaylistItem *>( m_playlistItem->itemAbove() );
165 loadTags( m_playlistItem->url() );
167 else
169 storeTags( *m_currentURL );
171 if( m_currentURL != m_urlList.begin() )
172 --m_currentURL;
173 loadTags( *m_currentURL );
174 enableItems();
176 readTags();
180 inline void
181 TagDialog::nextTrack()
183 if( m_playlistItem )
185 if( !m_playlistItem->itemBelow() ) return;
187 storeTags();
189 m_playlistItem = static_cast<PlaylistItem *>( m_playlistItem->itemBelow() );
191 loadTags( m_playlistItem->url() );
193 else
195 storeTags( *m_currentURL );
197 KUrl::List::iterator next = m_currentURL;
198 ++next;
199 if( next != m_urlList.end() )
200 ++m_currentURL;
201 loadTags( *m_currentURL );
202 enableItems();
204 readTags();
207 inline void
208 TagDialog::perTrack()
210 m_perTrack = !m_perTrack;
211 if( m_perTrack )
213 // just switched to per track mode
214 applyToAllTracks();
215 setSingleTrackMode();
216 loadTags( *m_currentURL );
217 readTags();
219 else
221 storeTags( *m_currentURL );
222 setMultipleTracksMode();
223 readMultipleTracks();
226 enableItems();
230 void
231 TagDialog::enableItems()
233 checkBox_perTrack->setChecked( m_perTrack );
234 pushButton_previous->setEnabled( m_perTrack && m_currentURL != m_urlList.begin() );
235 KUrl::List::ConstIterator next = m_currentURL;
236 ++next;
237 pushButton_next->setEnabled( m_perTrack && next != m_urlList.end());
238 if( m_urlList.count() == 1 )
240 checkBox_perTrack->setEnabled( false );
242 else
244 checkBox_perTrack->setEnabled( true );
249 inline void
250 TagDialog::checkModified() //SLOT
252 pushButton_ok->setEnabled( hasChanged() || storedTags.count() > 0 || storedScores.count() > 0
253 || storedLyrics.count() > 0 || storedRatings.count() > 0 || newLabels.count() > 0 );
256 void
257 TagDialog::loadCover( const QString &artist, const QString &album )
259 if ( m_bundle.artist() != artist || m_bundle.album()!=album )
260 return;
262 // draw the album cover on the dialog
263 QString cover = CollectionDB::instance()->albumImage( m_bundle );
265 if( m_currentCover != cover )
267 pixmap_cover->setPixmap( QPixmap( cover, "PNG" ) );
268 m_currentCover = cover;
270 pixmap_cover->setInformation( m_bundle.artist(), m_bundle.album() );
271 const int s = AmarokConfig::coverPreviewSize();
272 pixmap_cover->setMinimumSize( s, s );
273 pixmap_cover->setMaximumSize( s, s );
277 void
278 TagDialog::setFileNameSchemes() //SLOT
280 KDialog *kDialog = new KDialog(this);
281 Ui::TagGuesserConfigDialog* dialog = new Ui::TagGuesserConfigDialog();
282 dialog->setupUi(kDialog);
283 kDialog->exec();
284 delete dialog;
288 void
289 TagDialog::guessFromFilename() //SLOT
291 int cur = 0;
293 TagGuesser guesser( m_bundle.url().path() );
294 if( !guesser.title().isNull() )
295 kLineEdit_title->setText( guesser.title() );
297 if( !guesser.artist().isNull() )
299 cur = kComboBox_artist->currentIndex();
300 kComboBox_artist->setItemText( cur, guesser.artist() );
303 if( !guesser.album().isNull() )
305 cur = kComboBox_album->currentIndex();
306 kComboBox_album->setItemText( cur, guesser.album() );
309 if( !guesser.track().isNull() )
310 qSpinBox_track->setValue( guesser.track().toInt() );
311 if( !guesser.comment().isNull() )
312 kTextEdit_comment->setText( guesser.comment() );
313 if( !guesser.year().isNull() )
314 qSpinBox_year->setValue( guesser.year().toInt() );
316 if( !guesser.composer().isNull() )
318 cur = kComboBox_composer->currentIndex();
319 kComboBox_composer->setItemText( cur, guesser.composer() );
322 if( !guesser.genre().isNull() )
324 cur = kComboBox_genre->currentIndex();
325 kComboBox_genre->setItemText( cur, guesser.genre() );
329 void
330 TagDialog::musicbrainzQuery() //SLOT
332 #if HAVE_TUNEPIMP
333 kDebug() ;
335 m_mbTrack = m_bundle.url();
336 KTRMLookup* ktrm = new KTRMLookup( m_mbTrack.path(), true );
337 connect( ktrm, SIGNAL( sigResult( KTRMResultList, QString ) ), SLOT( queryDone( KTRMResultList, QString ) ) );
338 connect( pushButton_cancel, SIGNAL( clicked() ), ktrm, SLOT( deleteLater() ) );
340 pushButton_musicbrainz->setEnabled( false );
341 pushButton_musicbrainz->setText( i18n( "Generating audio fingerprint..." ) );
342 QApplication::setOverrideCursor( Qt::BusyCursor );
343 #endif
346 void
347 TagDialog::queryDone( KTRMResultList results, QString error ) //SLOT
349 #if HAVE_TUNEPIMP
351 if ( !error.isEmpty() ) {
352 KMessageBox::sorry( this, i18n( "Tunepimp (MusicBrainz tagging library) returned the following error: \"%1\".", error) );
354 else {
355 if ( !results.isEmpty() )
357 TrackPickerDialog* t = new TrackPickerDialog( m_mbTrack.fileName(), results, this );
358 t->show();
359 connect( t, SIGNAL( finished() ), SLOT( resetMusicbrainz() ) ); // clear m_mbTrack
361 else {
362 KMessageBox::sorry( this, i18n( "The track was not found in the MusicBrainz database." ) );
363 resetMusicbrainz(); // clear m_mbTrack
367 QApplication::restoreOverrideCursor();
368 pushButton_musicbrainz->setEnabled( true );
369 pushButton_musicbrainz->setText( m_buttonMbText );
370 #else
371 Q_UNUSED(results);
372 Q_UNUSED(error);
373 #endif
376 void
377 TagDialog::fillSelected( KTRMResult selected ) //SLOT
379 #if HAVE_TUNEPIMP
380 kDebug() ;
383 if ( m_bundle.url() == m_mbTrack ) {
384 if ( !selected.title().isEmpty() ) kLineEdit_title->setText( selected.title() );
385 if ( !selected.artist().isEmpty() ) kComboBox_artist->setCurrentText( selected.artist() );
386 if ( !selected.album().isEmpty() ) kComboBox_album->setCurrentText( selected.album() );
387 if ( selected.track() != 0 ) qSpinBox_track->setValue( selected.track() );
388 if ( selected.year() != 0 ) qSpinBox_year->setValue( selected.year() );
389 } else {
390 MetaBundle mb;
391 mb.setPath( m_mbTrack.path() );
392 if ( !selected.title().isEmpty() ) mb.setTitle( selected.title() );
393 if ( !selected.artist().isEmpty() ) mb.setArtist( selected.artist() );
394 if ( !selected.album().isEmpty() ) mb.setAlbum( selected.album() );
395 if ( selected.track() != 0 ) mb.setTrack( selected.track() );
396 if ( selected.year() != 0 ) mb.setYear( selected.year() );
398 storedTags.replace( m_mbTrack.path(), mb );
400 #else
401 Q_UNUSED(selected);
402 #endif
405 void TagDialog::resetMusicbrainz() //SLOT
407 #if HAVE_TUNEPIMP
408 m_mbTrack = "";
409 #endif
412 ////////////////////////////////////////////////////////////////////////////////
413 // PRIVATE
414 ////////////////////////////////////////////////////////////////////////////////
416 void TagDialog::init()
418 DEBUG_BLOCK
421 // delete itself when closing
422 setAttribute( Qt::WA_DeleteOnClose );
424 KConfigGroup config = Amarok::config( "TagDialog" );
426 kTabWidget->addTab( summaryTab, i18n( "Summary" ) );
427 kTabWidget->addTab( tagsTab, i18n( "Tags" ) );
428 kTabWidget->addTab( lyricsTab, i18n( "Lyrics" ) );
429 kTabWidget->addTab( statisticsTab, i18n( "Statistics" ) );
430 kTabWidget->addTab( labelsTab, i18n( "Labels" ) );
431 kTabWidget->setCurrentIndex( config.readEntry( "CurrentTab", 0 ) );
433 int items = kComboBox_artist->count();
434 const QStringList artists = CollectionDB::instance()->artistList();
435 kComboBox_artist->insertItems( items, artists );
436 kComboBox_artist->completionObject()->insertItems( artists );
437 kComboBox_artist->completionObject()->setIgnoreCase( true );
438 kComboBox_artist->setCompletionMode( KGlobalSettings::CompletionPopup );
440 items = kComboBox_album->count();
441 const QStringList albums = CollectionDB::instance()->albumList();
442 kComboBox_album->insertItems( items, albums );
443 kComboBox_album->completionObject()->insertItems( albums );
444 kComboBox_album->completionObject()->setIgnoreCase( true );
445 kComboBox_album->setCompletionMode( KGlobalSettings::CompletionPopup );
447 items = kComboBox_artist->count();
448 const QStringList composers = CollectionDB::instance()->composerList();
449 kComboBox_composer->insertItems( items, composers );
450 kComboBox_composer->completionObject()->insertItems( composers );
451 kComboBox_composer->completionObject()->setIgnoreCase( true );
452 kComboBox_composer->setCompletionMode( KGlobalSettings::CompletionPopup );
454 items = kComboBox_artist->count();
455 kComboBox_rating->insertItems( items, MetaBundle::ratingList() );
457 // const QStringList genres = MetaBundle::genreList();
458 items = kComboBox_artist->count();
459 const QStringList genres = CollectionDB::instance()->genreList();
460 kComboBox_genre->insertItems( items, genres );
461 kComboBox_genre->completionObject()->insertItems( genres );
462 kComboBox_genre->completionObject()->setIgnoreCase( true );
464 const QStringList labels = CollectionDB::instance()->labelList();
465 //TODO: figure out a way to add auto-completion support to kTestEdit_selectedLabels
467 m_labelCloud = new KHTMLPart( labels_favouriteLabelsFrame );
468 //m_labelCloud = new HTMLView( labels_favouriteLabelsFrame );
469 //m_labelCloud->view()->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored, false );
470 QSizePolicy policy(QSizePolicy::Ignored, QSizePolicy::Ignored);
471 m_labelCloud->view()->setSizePolicy(policy);
473 //m_labelCloud->view()->setVScrollBarMode( Q3ScrollView::AlwaysOff );
474 //m_labelCloud->view()->setHScrollBarMode( Q3ScrollView::AlwaysOff );
476 new QVBoxLayout( labels_favouriteLabelsFrame );
477 labels_favouriteLabelsFrame->layout()->addWidget( m_labelCloud->view() );
478 const QStringList favoriteLabels = CollectionDB::instance()->favoriteLabels();
479 QString html = generateHTML( favoriteLabels );
480 m_labelCloud->write( html );
481 connect( m_labelCloud->browserExtension(), SIGNAL( openUrlRequest( const KUrl &, const KParts::OpenUrlArguments&, const KParts::BrowserArguments& ) ),
482 this, SLOT( openUrlRequest( const KUrl & ) ) );
484 // looks better to have a blank label than 0, we can't do this in
485 // the UI file due to bug in Designer
486 qSpinBox_track->setSpecialValueText( " " );
487 qSpinBox_year->setSpecialValueText( " " );
488 qSpinBox_score->setSpecialValueText( " " );
489 qSpinBox_discNumber->setSpecialValueText( " " );
491 if( !AmarokConfig::useRatings() )
493 kComboBox_rating->hide();
494 ratingLabel->hide();
496 if( !AmarokConfig::useScores() )
498 qSpinBox_score->hide();
499 scoreLabel->hide();
502 //HACK due to deficiency in Qt that will be addressed in version 4
503 // QSpinBox doesn't emit valueChanged if you edit the value with
504 // the lineEdit until you change the keyboard focus
505 connect( qSpinBox_year, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
506 connect( qSpinBox_track, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
507 connect( qSpinBox_score, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
508 connect( qSpinBox_discNumber, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
510 // Connects for modification check
511 connect( kLineEdit_title, SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
512 connect( kComboBox_composer,SIGNAL(activated( int )), SLOT(checkModified()) );
513 connect( kComboBox_composer,SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
514 connect( kComboBox_artist, SIGNAL(activated( int )), SLOT(checkModified()) );
515 connect( kComboBox_artist, SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
516 connect( kComboBox_album, SIGNAL(activated( int )), SLOT(checkModified()) );
517 connect( kComboBox_album, SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
518 connect( kComboBox_genre, SIGNAL(activated( int )), SLOT(checkModified()) );
519 connect( kComboBox_genre, SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
520 connect( kComboBox_rating, SIGNAL(activated( int )), SLOT(checkModified()) );
521 connect( kComboBox_rating, SIGNAL(textChanged( const QString& )), SLOT(checkModified()) );
522 connect( qSpinBox_track, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
523 connect( qSpinBox_year, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
524 connect( qSpinBox_score, SIGNAL(valueChanged( int )), SLOT(checkModified()) );
525 connect( kTextEdit_comment, SIGNAL(textChanged()), SLOT(checkModified()) );
526 connect( kTextEdit_lyrics, SIGNAL(textChanged()), SLOT(checkModified()) );
527 connect( kTextEdit_selectedLabels, SIGNAL(textChanged()), SLOT(checkModified()) );
529 // Remember original button text
530 m_buttonMbText = pushButton_musicbrainz->text();
532 connect( pushButton_cancel, SIGNAL(clicked()), SLOT(cancelPressed()) );
533 connect( pushButton_ok, SIGNAL(clicked()), SLOT(accept()) );
534 connect( pushButton_open, SIGNAL(clicked()), SLOT(openPressed()) );
535 connect( pushButton_previous, SIGNAL(clicked()), SLOT(previousTrack()) );
536 connect( pushButton_next, SIGNAL(clicked()), SLOT(nextTrack()) );
537 connect( checkBox_perTrack, SIGNAL(clicked()), SLOT(perTrack()) );
539 // set an icon for the open-in-konqui button
540 pushButton_open->setIcon( KIcon( Amarok::icon( "files" ) ) );
542 //Update lyrics on Context Browser
543 // connect( this, SIGNAL(lyricsChanged( const QString& )), ContextBrowser::instance(), SLOT( lyricsChanged( const QString& ) ) );
545 //Update cover
546 connect( CollectionDB::instance(), SIGNAL( coverFetched( const QString&, const QString& ) ),
547 this, SLOT( loadCover( const QString&, const QString& ) ) );
548 connect( CollectionDB::instance(), SIGNAL( coverChanged( const QString&, const QString& ) ),
549 this, SLOT( loadCover( const QString&, const QString& ) ) );
553 #if HAVE_TUNEPIMP
554 connect( pushButton_musicbrainz, SIGNAL(clicked()), SLOT(musicbrainzQuery()) );
555 #else
556 pushButton_musicbrainz->setToolTip( i18n("Please install MusicBrainz to enable this functionality") );
557 #endif
559 connect( pushButton_guessTags, SIGNAL(clicked()), SLOT( guessFromFilename() ) );
560 connect( pushButton_setFilenameSchemes, SIGNAL(clicked()), SLOT( setFileNameSchemes() ) );
562 if( m_tracks.count() ) { //editing multiple tracks
563 m_perTrack = false;
564 setMultipleTracksMode();
565 readMultipleTracks();
567 checkBox_perTrack->setChecked( m_perTrack );
568 if( m_urlList.count() == 1 )
570 checkBox_perTrack->setEnabled( false );
571 pushButton_previous->setEnabled( false );
572 pushButton_next->setEnabled( false );
574 else
576 checkBox_perTrack->setEnabled( true );
577 pushButton_previous->setEnabled( m_perTrack );
578 pushButton_next->setEnabled( m_perTrack );
581 else
583 m_perTrack = true;
584 checkBox_perTrack->hide();
586 if( !m_playlistItem ) {
587 //We have already loaded the metadata (from the file) in the constructor
588 pushButton_previous->hide();
589 pushButton_next->hide();
591 else
593 //Reload the metadata from the file, to be sure it's accurate
594 loadTags( m_playlistItem->url() );
597 loadLyrics( m_bundle.url() );
598 loadLabels( m_bundle.url() );
599 readTags();
603 // make it as small as possible
604 resize( sizeHint().width(), minimumSize().height() );
609 inline const QString TagDialog::unknownSafe( QString s ) {
610 return ( s.isNull() || s.isEmpty() || s == "?" || s == "-" )
611 ? i18n ( "Unknown" )
612 : s;
615 const QStringList TagDialog::statisticsData() {
617 QStringList data, values;
618 const uint artist_id = CollectionDB::instance()->artistID( m_bundle.artist() );
619 const uint album_id = CollectionDB::instance()->albumID ( m_bundle.album() );
621 QueryBuilder qb;
623 if ( !m_bundle.artist().isEmpty() ) {
624 // tracks by this artist
625 qb.clear();
626 qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabSong, QueryBuilder::valTitle );
627 qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, QString::number( artist_id ) );
628 values = qb.run();
629 data += i18n( "Tracks by this Artist" );
630 data += values[0];
633 // albums by this artist
634 qb.clear();
635 qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabAlbum, QueryBuilder::valID );
636 qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, QString::number( artist_id ) );
637 qb.groupBy( QueryBuilder::tabSong, QueryBuilder::valAlbumID );
638 qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) );
639 qb.setOptions( QueryBuilder::optNoCompilations );
640 values = qb.run();
641 data += i18n( "Albums by this Artist" );
642 data += QString::number( values.count() );
645 // Favorite track by this artist
646 qb.clear();
647 qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
648 qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore );
649 qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, QString::number( artist_id ) );
650 qb.sortByFavorite();
651 qb.setLimit( 0, 1 );
652 values = qb.run();
653 data += i18n( "Favorite by this Artist" );
654 data += values.isEmpty() ? QString() : values[0];
656 if ( !m_bundle.album().isEmpty() ) {
657 // Favorite track on this album
658 qb.clear();
659 qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
660 qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore );
661 qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, QString::number( album_id ) );
662 qb.sortByFavorite();
663 qb.setLimit( 0, 1 );
664 values = qb.run();
665 data += i18n( "Favorite on this Album" );
666 data += values.isEmpty() ? QString() : values[0];
669 // Related Artists
670 const QString sArtists = CollectionDB::instance()->similarArtists( m_bundle.artist(), 4 ).join(", ");
671 if ( !sArtists.isEmpty() ) {
672 data += i18n( "Related Artists" );
673 data += sArtists;
676 return data;
679 void TagDialog::readTags()
681 bool local = m_bundle.url().isLocalFile();
683 setWindowTitle( KDialog::makeStandardCaption( i18n("Track Information: %1 by %2",
684 m_bundle.title(), m_bundle.artist() ) ) );
686 QString niceTitle;
687 if ( m_bundle.album().isEmpty() ) {
688 if( !m_bundle.title().isEmpty() ) {
689 if( !m_bundle.artist().isEmpty() )
690 niceTitle = i18n( "<b>%1</b> by <b>%2</b>", m_bundle.title(), m_bundle.artist() );
691 else
692 niceTitle = QString( "<b>%1</b>").arg( m_bundle.title() );
694 else niceTitle = m_bundle.prettyTitle();
696 else {
697 niceTitle = i18n( "<b>%1</b> by <b>%2</b> on <b>%3</b>" ,
698 m_bundle.title(), m_bundle.artist(), m_bundle.album() );
700 trackArtistAlbumLabel->setText( niceTitle );
701 trackArtistAlbumLabel2->setText( niceTitle );
703 kLineEdit_title->setText( m_bundle.title() );
704 kComboBox_artist->setItemText( kComboBox_artist->currentIndex(), m_bundle.artist() );
705 kComboBox_album->setItemText( kComboBox_album->currentIndex(), m_bundle.album() );
706 kComboBox_genre->setItemText( kComboBox_genre->currentIndex(), m_bundle.genre() );
707 kComboBox_rating->setCurrentIndex( m_bundle.rating() ? m_bundle.rating() - 1 : 0 );
708 qSpinBox_track->setValue( m_bundle.track() );
709 kComboBox_composer->setItemText( kComboBox_composer->currentIndex(), m_bundle.composer() );
710 qSpinBox_year->setValue( m_bundle.year() );
711 qSpinBox_score->setValue( static_cast<int>(m_bundle.score()) );
712 qSpinBox_discNumber->setValue( m_bundle.discNumber() );
713 kTextEdit_comment->setText( m_bundle.comment() );
715 bool extended = m_bundle.hasExtendedMetaInformation();
716 qSpinBox_discNumber->setEnabled( extended );
717 kComboBox_composer->setEnabled( extended );
720 QString summaryText, statisticsText;
721 const QString body2cols = "<tr><td><nobr>%1</nobr></td><td><b>%2</b></td></tr>";
722 const QString body1col = "<tr><td colspan=2>%1</td></td></tr>";
723 const QString emptyLine = "<tr><td colspan=2></td></tr>";
725 summaryText = "<table width=100%><tr><td width=50%><table>";
726 summaryText += body2cols.arg( i18n("Length:"), unknownSafe( m_bundle.prettyLength() ) );
727 summaryText += body2cols.arg( i18n("Bitrate:"), unknownSafe( m_bundle.prettyBitrate() ) );
728 summaryText += body2cols.arg( i18n("Samplerate:"), unknownSafe( m_bundle.prettySampleRate() ) );
729 summaryText += body2cols.arg( i18n("Size:"), unknownSafe( m_bundle.prettyFilesize() ) );
730 summaryText += body2cols.arg( i18n("Format:"), unknownSafe( m_bundle.type() ) );
732 summaryText += "</table></td><td width=50%><table>";
733 if( AmarokConfig::useScores() )
734 summaryText += body2cols.arg( i18n("Score:"), QString::number( static_cast<int>( m_bundle.score() ) ) );
735 if( AmarokConfig::useRatings() )
736 summaryText += body2cols.arg( i18n("Rating:"), m_bundle.prettyRating() );
738 summaryText += body2cols.arg( i18n("Playcount:"), QString::number( m_bundle.playCount() ) );
739 summaryText += body2cols.arg( i18n("First Played:"),
740 m_bundle.playCount() ? KGlobal::locale()->formatDate( CollectionDB::instance()->getFirstPlay( m_bundle.url().path() ).date() , KLocale::ShortDate ) : i18n("Never") );
741 summaryText += body2cols.arg( i18nc("a single item (singular)", "Last Played:"),
742 m_bundle.playCount() ? KGlobal::locale()->formatDate( CollectionDB::instance()->getLastPlay( m_bundle.url().path() ).date() , KLocale::ShortDate ) : i18n("Never") );
744 summaryText += "</table></td></tr></table>";
745 summaryLabel->setText( summaryText );
747 statisticsText = "<table>";
749 QStringList sData = statisticsData();
750 for ( int i = 0; i < sData.count(); i+=2 ) {
751 statisticsText += body2cols.arg( sData[i], sData[i+1] );
754 statisticsText += "</table>";
756 statisticsLabel->setText( statisticsText );
758 kLineEdit_location->setText( local ? m_bundle.url().path() : m_bundle.url().url() );
760 //lyrics
761 kTextEdit_lyrics->setText( m_lyrics );
763 loadCover( m_bundle.artist(), m_bundle.album() );
766 // enable only for local files
767 kLineEdit_title->setReadOnly( !local );
768 kComboBox_artist->setEnabled( local );
769 kComboBox_album->setEnabled( local );
770 kComboBox_genre->setEnabled( local );
771 kComboBox_rating->setEnabled( local );
772 qSpinBox_track->setEnabled( local );
773 qSpinBox_year->setEnabled( local );
774 qSpinBox_score->setEnabled( local );
775 kTextEdit_comment->setEnabled( local );
776 kTextEdit_selectedLabels->setEnabled( local );
777 m_labelCloud->view()->setEnabled( local );
779 if( local )
781 pushButton_musicbrainz->show();
782 pushButton_guessTags->show();
783 pushButton_setFilenameSchemes->show();
785 else
787 pushButton_musicbrainz->hide();
788 pushButton_guessTags->hide();
789 pushButton_setFilenameSchemes->hide();
792 // If it's a local file, write the directory to m_path, else disable the "open in konqui" button
793 if ( local )
794 m_path = m_bundle.url().directory();
795 else
796 pushButton_open->setEnabled( false );
798 pushButton_ok->setEnabled( storedTags.count() > 0 || storedScores.count() > 0
799 || storedLyrics.count() > 0 || storedRatings.count() > 0
800 || newLabels.count() > 0 );
802 #if HAVE_TUNEPIMP
803 // Don't enable button if a query is in progress already (or if the file isn't local)
804 pushButton_musicbrainz->setEnabled( m_bundle.url().isLocalFile() && m_mbTrack.isEmpty() );
805 #else
806 pushButton_musicbrainz->setEnabled( false );
807 #endif
809 if( m_playlistItem ) {
810 pushButton_previous->setEnabled( m_playlistItem->itemAbove() );
811 pushButton_next->setEnabled( m_playlistItem->itemBelow() );
816 void
817 TagDialog::setMultipleTracksMode()
820 kTabWidget->setTabEnabled( kTabWidget->indexOf(summaryTab), false );
821 kTabWidget->setTabEnabled( kTabWidget->indexOf(lyricsTab), false );
823 kComboBox_artist->setItemText( kComboBox_artist->currentIndex(), "" );
824 kComboBox_album->setItemText( kComboBox_album->currentIndex(), "" );
825 kComboBox_genre->setItemText( kComboBox_genre->currentIndex(), "" );
826 kComboBox_composer->setItemText( kComboBox_composer->currentIndex(), "" );
827 kLineEdit_title->setText( "" );
828 kTextEdit_comment->setText( "" );
829 qSpinBox_track->setValue( qSpinBox_track->minimum() );
830 qSpinBox_discNumber->setValue( qSpinBox_discNumber->minimum() );
831 qSpinBox_year->setValue( qSpinBox_year->minimum() );
833 qSpinBox_score->setValue( qSpinBox_score->minimum() );
834 kComboBox_rating->setCurrentItem( 0 );
836 kLineEdit_title->setEnabled( false );
837 qSpinBox_track->setEnabled( false );
839 pushButton_musicbrainz->hide();
840 pushButton_guessTags->hide();
841 pushButton_setFilenameSchemes->hide();
843 locationLabel->hide();
844 kLineEdit_location->hide();
845 pushButton_open->hide();
846 pixmap_cover->hide();
849 void
850 TagDialog::setSingleTrackMode()
853 kTabWidget->setTabEnabled( kTabWidget->indexOf(summaryTab), true );
854 kTabWidget->setTabEnabled( kTabWidget->indexOf(lyricsTab), true );
856 kLineEdit_title->setEnabled( true );
857 qSpinBox_track->setEnabled( true );
859 pushButton_musicbrainz->show();
860 pushButton_guessTags->show();
861 pushButton_setFilenameSchemes->show();
863 locationLabel->show();
864 kLineEdit_location->show();
865 pushButton_open->show();
866 pixmap_cover->show();
870 void
871 TagDialog::readMultipleTracks()
874 setWindowTitle( KDialog::makeStandardCaption( i18np("1 Track", "Information for %1 Tracks", m_urlList.count()) ) );
876 //Check which fields are the same for all selected tracks
877 const KUrl::List::ConstIterator end = m_urlList.end();
878 KUrl::List::ConstIterator it = m_urlList.begin();
880 m_bundle = MetaBundle();
882 MetaBundle first = bundleForURL( *it );
884 bool artist=true, album=true, genre=true, comment=true, year=true,
885 score=true, rating=true, composer=true, discNumber=true;
886 int songCount=0, ratingCount=0, ratingSum=0, scoreCount=0;
887 float scoreSum = 0.f;
888 for ( ; it != end; ++it ) {
889 MetaBundle mb = bundleForURL( *it );
890 songCount++;
891 if ( mb.rating() ) {
892 ratingCount++;
893 ratingSum+=mb.rating();
895 if ( mb.score() > 0.f ) {
896 scoreCount++;
897 scoreSum+=mb.score();
900 if( !mb.url().isLocalFile() ) {
901 // If we have a non local file, don't even lose more time comparing
902 artist = album = genre = comment = year = false;
903 score = rating = composer = discNumber = false;
904 continue;
906 if ( artist && mb.artist()!=first.artist() )
907 artist=false;
908 if ( album && mb.album()!=first.album() )
909 album=false;
910 if ( genre && mb.genre()!=first.genre() )
911 genre=false;
912 if ( comment && mb.comment()!=first.comment() )
913 comment=false;
914 if ( year && mb.year()!=first.year() )
915 year=false;
916 if ( composer && mb.composer()!=first.composer() )
917 composer=false;
918 if ( discNumber && mb.discNumber()!=first.discNumber() )
919 discNumber=false;
920 if ( score && mb.score()!=first.score() )
921 score = false;
922 if ( rating && mb.rating()!=first.rating() )
923 rating = false;
925 // Set them in the dialog and in m_bundle ( so we don't break hasChanged() )
926 int cur_item;
927 if (artist) {
928 cur_item = kComboBox_artist->currentIndex();
929 m_bundle.setArtist( first.artist() );
930 kComboBox_artist->setItemText( cur_item, first.artist() );
932 if (album) {
933 cur_item = kComboBox_album->currentIndex();
934 m_bundle.setAlbum( first.album() );
935 kComboBox_album->setItemText( cur_item, first.album() );
937 if (genre) {
938 cur_item = kComboBox_genre->currentIndex();
939 m_bundle.setGenre( first.genre() );
940 kComboBox_genre->setItemText( cur_item, first.genre() );
942 if (comment) {
943 m_bundle.setComment( first.comment() );
944 kTextEdit_comment->setText( first.comment() );
946 if (composer) {
947 cur_item = kComboBox_composer->currentIndex();
948 m_bundle.setComposer( first.composer() );
949 kComboBox_composer->setItemText( cur_item, first.composer() );
951 if (year) {
952 m_bundle.setYear( first.year() );
953 qSpinBox_year->setValue( first.year() );
955 if (discNumber) {
956 m_bundle.setDiscNumber( first.discNumber() );
957 qSpinBox_discNumber->setValue( first.discNumber() );
959 if (score) {
960 m_bundle.setScore( first.score() );
961 qSpinBox_score->setValue( static_cast<int>( first.score() ) );
963 if (rating) {
964 m_bundle.setRating( first.rating() );
965 kComboBox_rating->setCurrentIndex( first.rating() ? first.rating() - 1 : 0 );
968 m_currentURL = m_urlList.begin();
970 trackArtistAlbumLabel2->setText( i18np( "Editing 1 file", "Editing %1 files", songCount ) );
972 const QString body = "<tr><td><nobr>%1:</nobr></td><td><b>%2</b></td></tr>";
973 QString statisticsText = "<table>";
975 if( AmarokConfig::useRatings() ) {
976 statisticsText += body.arg( i18n( "Rated Songs:" ) , QString::number( ratingCount ) );
977 if ( ratingCount )
978 statisticsText += body.arg( i18n( "Average Rating:" ) , QString::number( (float)ratingSum / (float)ratingCount/2.0, 'f', 1 ) );
981 if( AmarokConfig::useRatings() ) {
982 statisticsText += body.arg( i18n( "Scored Songs:" ) , QString::number( scoreCount ) );
983 if ( scoreCount )
984 statisticsText += body.arg( i18n( "Average Score:" ) , QString::number( scoreSum / scoreCount, 'f', 1 ) );
988 statisticsText += "</table>";
990 statisticsLabel->setText( statisticsText );
992 QStringList commonLabels = getCommonLabels();
993 QString text;
994 oldForeach ( commonLabels )
996 if ( !text.isEmpty() )
997 text.append( ", " );
998 text.append( *it );
1000 kTextEdit_selectedLabels->setText( text );
1001 m_commaSeparatedLabels = text;
1003 // This will reset a wrongly enabled Ok button
1004 checkModified();
1007 QStringList
1008 TagDialog::getCommonLabels()
1010 DEBUG_BLOCK
1011 QMap<QString, int> counterMap;
1012 const KUrl::List::ConstIterator end = m_urlList.end();
1013 KUrl::List::ConstIterator iter = m_urlList.begin();
1014 for(; iter != end; ++iter )
1016 QStringList labels = labelsForURL( *iter );
1017 oldForeach( labels )
1019 if ( counterMap.contains( *it ) )
1020 counterMap[ *it ] = counterMap[ *it ] +1;
1021 else
1022 counterMap[ *it ] = 1;
1025 int n = m_urlList.count();
1026 QStringList result;
1027 QMap<QString, int>::ConstIterator counterEnd( counterMap.end() );
1028 for(QMap<QString, int>::ConstIterator it = counterMap.begin(); it != counterEnd; ++it )
1030 if ( it.value() == n )
1031 result.append( it.key() );
1033 return result;
1036 inline bool
1037 equalString( const QString &a, const QString &b )
1039 return (a.isEmpty() && b.isEmpty()) ? true : a == b;
1042 bool
1043 TagDialog::hasChanged()
1045 return changes();
1049 TagDialog::changes()
1051 int result=TagDialog::NOCHANGE;
1052 bool modified = false;
1053 modified |= !equalString( kComboBox_artist->lineEdit()->text(), m_bundle.artist() );
1054 modified |= !equalString( kComboBox_album->lineEdit()->text(), m_bundle.album() );
1055 modified |= !equalString( kComboBox_genre->lineEdit()->text(), m_bundle.genre() );
1056 modified |= qSpinBox_year->value() != m_bundle.year();
1057 modified |= qSpinBox_discNumber->value() != m_bundle.discNumber();
1058 modified |= !equalString( kComboBox_composer->lineEdit()->text(), m_bundle.composer() );
1060 modified |= !equalString( kTextEdit_comment->toPlainText(), m_bundle.comment() );
1062 if (!m_urlList.count() || m_perTrack) { //ignore these on MultipleTracksMode
1063 modified |= !equalString( kLineEdit_title->text(), m_bundle.title() );
1064 modified |= qSpinBox_track->value() != m_bundle.track();
1066 if (modified)
1067 result |= TagDialog::TAGSCHANGED;
1069 if (qSpinBox_score->value() != m_bundle.score())
1070 result |= TagDialog::SCORECHANGED;
1071 if (kComboBox_rating->currentIndex() != ( m_bundle.rating() ? m_bundle.rating() - 1 : 0 ) )
1072 result |= TagDialog::RATINGCHANGED;
1074 if (!m_urlList.count() || m_perTrack) { //ignore these on MultipleTracksMode
1075 if ( !equalString( kTextEdit_lyrics->toPlainText(), m_lyrics ) )
1076 result |= TagDialog::LYRICSCHANGED;
1079 if ( !equalString( kTextEdit_selectedLabels->toPlainText(), m_commaSeparatedLabels ) )
1080 result |= TagDialog::LABELSCHANGED;
1082 return result;
1085 void
1086 TagDialog::storeTags()
1088 storeTags( m_bundle.url() );
1091 void
1092 TagDialog::storeTags( const KUrl &kurl )
1094 int result = changes();
1095 QString url = kurl.path();
1096 if( result & TagDialog::TAGSCHANGED ) {
1097 MetaBundle mb( m_bundle );
1099 mb.setTitle( kLineEdit_title->text() );
1100 mb.setComposer( kComboBox_composer->currentText() );
1101 mb.setArtist( kComboBox_artist->currentText() );
1102 mb.setAlbum( kComboBox_album->currentText() );
1103 mb.setComment( kTextEdit_comment->toPlainText() );
1104 mb.setGenre( kComboBox_genre->currentText() );
1105 mb.setTrack( qSpinBox_track->value() );
1106 mb.setYear( qSpinBox_year->value() );
1107 mb.setDiscNumber( qSpinBox_discNumber->value() );
1108 mb.setLength( m_bundle.length() );
1109 mb.setBitrate( m_bundle.bitrate() );
1110 mb.setSampleRate( m_bundle.sampleRate() );
1111 storedTags.remove( url );
1112 storedTags.insert( url, mb );
1114 if( result & TagDialog::SCORECHANGED ) {
1115 storedScores.remove( url );
1116 storedScores.insert( url, qSpinBox_score->value() );
1119 if( result & TagDialog::RATINGCHANGED ) {
1120 storedRatings.remove( url );
1121 storedRatings.insert( url, kComboBox_rating->currentIndex() ? kComboBox_rating->currentIndex() + 1 : 0 );
1124 if( result & TagDialog::LYRICSCHANGED ) {
1125 if ( kTextEdit_lyrics->toPlainText().isEmpty() ) {
1126 storedLyrics.remove( url );
1127 storedLyrics.insert( url, QString() );
1129 else {
1130 QDomDocument doc;
1131 QDomElement e = doc.createElement( "lyrics" );
1132 e.setAttribute( "artist", kComboBox_artist->currentText() );
1133 e.setAttribute( "title", kLineEdit_title->text() );
1134 QDomText t = doc.createTextNode( kTextEdit_lyrics->toPlainText() );
1135 e.appendChild( t );
1136 doc.appendChild( e );
1137 storedLyrics.remove( url );
1138 storedLyrics.insert( url, doc.toString() );
1141 if( result & TagDialog::LABELSCHANGED ) {
1142 generateDeltaForLabelList( labelListFromText( kTextEdit_selectedLabels->toPlainText() ) );
1143 QStringList tmpLabels;
1144 if ( newLabels.find( url ) != newLabels.end() )
1145 tmpLabels = newLabels[ url ];
1146 else
1147 tmpLabels = originalLabels[ url ];
1148 //apply delta
1149 oldForeach( m_removedLabels )
1151 tmpLabels.removeAt( tmpLabels.indexOf(*it) );
1153 oldForeach( m_addedLabels )
1155 // this just feels dirty...
1156 if( tmpLabels.indexOf( *it ) == tmpLabels.indexOf( *tmpLabels.end() ) )
1157 tmpLabels.append( *it );
1159 newLabels.remove( url );
1160 newLabels.insert( url, tmpLabels );
1164 void
1165 TagDialog::storeTags( const KUrl &url, int changes, const MetaBundle &mb )
1167 if ( changes & TagDialog::TAGSCHANGED ) {
1168 storedTags.remove( url.path() );
1169 storedTags.insert( url.path(), mb );
1171 if ( changes & TagDialog::SCORECHANGED ) {
1172 storedScores.remove( url.path() );
1173 storedScores.insert( url.path(), mb.score() );
1175 if ( changes & TagDialog::RATINGCHANGED ) {
1176 storedRatings.remove( url.path() );
1177 storedRatings.insert( url.path(), mb.rating() );
1181 void
1182 TagDialog::storeLabels( const KUrl &url, const QStringList &labels )
1184 newLabels.remove( url.path() );
1185 newLabels.insert( url.path(), labels );
1189 void
1190 TagDialog::loadTags( const KUrl &url )
1192 m_bundle = bundleForURL( url );
1193 loadLyrics( url );
1194 loadLabels( url );
1197 void
1198 TagDialog::loadLyrics( const KUrl &url )
1200 QString xml = lyricsForURL(url.path() );
1202 QDomDocument doc;
1203 if( doc.setContent( xml ) )
1204 m_lyrics = doc.documentElement().text();
1205 else
1206 m_lyrics.clear();
1209 void
1210 TagDialog::loadLabels( const KUrl &url )
1212 DEBUG_BLOCK
1213 m_labels = labelsForURL( url );
1214 originalLabels[ url.path() ] = m_labels;
1215 QString text;
1216 oldForeach( m_labels )
1218 if ( !text.isEmpty() )
1219 text.append( ", " );
1220 text.append( *it );
1222 kTextEdit_selectedLabels->setText( text );
1223 m_commaSeparatedLabels = text;
1226 MetaBundle
1227 TagDialog::bundleForURL( const KUrl &url )
1229 if( storedTags.find( url.path() ) != storedTags.end() )
1230 return storedTags[ url.path() ];
1232 return MetaBundle( url, url.isLocalFile() );
1235 float
1236 TagDialog::scoreForURL( const KUrl &url )
1238 if( storedScores.find( url.path() ) != storedScores.end() )
1239 return storedScores[ url.path() ];
1241 return CollectionDB::instance()->getSongPercentage( url.path() );
1245 TagDialog::ratingForURL( const KUrl &url )
1247 if( storedRatings.find( url.path() ) != storedRatings.end() )
1248 return storedRatings[ url.path() ];
1250 return CollectionDB::instance()->getSongRating( url.path() );
1253 QString
1254 TagDialog::lyricsForURL( const KUrl &url )
1256 if( storedLyrics.find( url.path() ) != storedLyrics.end() )
1257 return storedLyrics[ url.path() ];
1259 return CollectionDB::instance()->getLyrics( url.path() );
1262 QStringList
1263 TagDialog::labelsForURL( const KUrl &url )
1265 if( newLabels.find( url.path() ) != newLabels.end() )
1266 return newLabels[ url.path() ];
1267 if( originalLabels.find( url.path() ) != originalLabels.end() )
1268 return originalLabels[ url.path() ];
1269 QStringList tmp = CollectionDB::instance()->getLabels( url.path(), CollectionDB::typeUser );
1270 originalLabels[ url.path() ] = tmp;
1271 return tmp;
1274 void
1275 TagDialog::saveTags()
1277 if( !m_perTrack )
1279 applyToAllTracks();
1281 else
1283 storeTags();
1286 QMap<QString, float>::ConstIterator endScore( storedScores.end() );
1287 for(QMap<QString, float>::ConstIterator it = storedScores.begin(); it != endScore; ++it ) {
1288 CollectionDB::instance()->setSongPercentage( it.key(), it.value() );
1290 QMap<QString, int>::ConstIterator endRating( storedRatings.end() );
1291 for(QMap<QString, int>::ConstIterator it = storedRatings.begin(); it != endRating; ++it ) {
1292 CollectionDB::instance()->setSongRating( it.key(), it.value() );
1294 QMap<QString, QString>::ConstIterator endLyrics( storedLyrics.end() );
1295 for(QMap<QString, QString>::ConstIterator it = storedLyrics.begin(); it != endLyrics; ++it ) {
1296 CollectionDB::instance()->setLyrics( it.key(), it.value(),
1297 CollectionDB::instance()->uniqueIdFromUrl( KUrl( it.key() ) ) );
1298 emit lyricsChanged( it.key() );
1300 QMap<QString, QStringList>::ConstIterator endLabels( newLabels.end() );
1301 for(QMap<QString, QStringList>::ConstIterator it = newLabels.begin(); it != endLabels; ++it ) {
1302 CollectionDB::instance()->setLabels( it.key(), it.value(),
1303 CollectionDB::instance()->uniqueIdFromUrl( KUrl( it.key() ) ), CollectionDB::typeUser );
1305 CollectionDB::instance()->cleanLabels();
1307 ThreadManager::instance()->queueJob( new TagDialogWriter( storedTags ) );
1311 void
1312 TagDialog::applyToAllTracks()
1314 generateDeltaForLabelList( labelListFromText( kTextEdit_selectedLabels->toPlainText() ) );
1316 const KUrl::List::ConstIterator end = m_urlList.end();
1317 for ( KUrl::List::ConstIterator it = m_urlList.begin(); it != end; ++it ) {
1319 /* we have to update the values if they changed, so:
1320 1) !kLineEdit_field->text().isEmpty() && kLineEdit_field->text() != mb.field
1321 i.e.: The user wrote something on the field, and it's different from
1322 what we have in the tag.
1323 2) !m_bundle.field().isEmpty() && kLineEdit_field->text().isEmpty()
1324 i.e.: The user was shown some value for the field (it was the same
1325 for all selected tracks), and he deliberately emptied it.
1326 TODO: All this mess is because the dialog uses "" to represent what the user
1327 doesn't want to change, maybe we can think of something better?
1330 MetaBundle mb = bundleForURL( *it );
1332 int changed = 0;
1333 if( !kComboBox_artist->currentText().isEmpty() && kComboBox_artist->currentText() != mb.artist() ||
1334 kComboBox_artist->currentText().isEmpty() && !m_bundle.artist().isEmpty() ) {
1335 mb.setArtist( kComboBox_artist->currentText() );
1336 changed |= TagDialog::TAGSCHANGED;
1339 if( !kComboBox_album->currentText().isEmpty() && kComboBox_album->currentText() != mb.album() ||
1340 kComboBox_album->currentText().isEmpty() && !m_bundle.album().isEmpty() ) {
1341 mb.setAlbum( kComboBox_album->currentText() );
1342 changed |= TagDialog::TAGSCHANGED;
1344 if( !kComboBox_genre->currentText().isEmpty() && kComboBox_genre->currentText() != mb.genre() ||
1345 kComboBox_genre->currentText().isEmpty() && !m_bundle.genre().isEmpty() ) {
1346 mb.setGenre( kComboBox_genre->currentText() );
1347 changed |= TagDialog::TAGSCHANGED;
1349 if( !kTextEdit_comment->toPlainText().isEmpty() && kTextEdit_comment->toPlainText() != mb.comment() ||
1350 kTextEdit_comment->toPlainText().isEmpty() && !m_bundle.comment().isEmpty() ) {
1351 mb.setComment( kTextEdit_comment->toPlainText() );
1352 changed |= TagDialog::TAGSCHANGED;
1354 if( !kComboBox_composer->currentText().isEmpty() && kComboBox_composer->currentText() != mb.composer() ||
1355 kComboBox_composer->currentText().isEmpty() && !m_bundle.composer().isEmpty() ) {
1356 mb.setComposer( kComboBox_composer->currentText() );
1357 changed |= TagDialog::TAGSCHANGED;
1360 if( qSpinBox_year->value() && qSpinBox_year->value() != mb.year() ||
1361 !qSpinBox_year->value() && m_bundle.year() ) {
1362 mb.setYear( qSpinBox_year->value() );
1363 changed |= TagDialog::TAGSCHANGED;
1365 if( qSpinBox_discNumber->value() && qSpinBox_discNumber->value() != mb.discNumber() ||
1366 !qSpinBox_discNumber->value() && m_bundle.discNumber() ) {
1367 mb.setDiscNumber( qSpinBox_discNumber->value() );
1368 changed |= TagDialog::TAGSCHANGED;
1371 if( qSpinBox_score->value() && qSpinBox_score->value() != mb.score() ||
1372 !qSpinBox_score->value() && m_bundle.score() )
1374 mb.setScore( qSpinBox_score->value() );
1375 changed |= TagDialog::SCORECHANGED;
1378 if( kComboBox_rating->currentIndex() && kComboBox_rating->currentIndex() != m_bundle.rating() - 1 ||
1379 !kComboBox_rating->currentIndex() && m_bundle.rating() )
1381 mb.setRating( kComboBox_rating->currentIndex() ? kComboBox_rating->currentIndex() + 1 : 0 );
1382 changed |= TagDialog::RATINGCHANGED;
1384 storeTags( *it, changed, mb );
1386 QStringList tmpLabels = labelsForURL( *it );
1387 //apply delta
1388 for( QStringList::Iterator iter = m_removedLabels.begin(); iter != m_removedLabels.end(); ++iter )
1390 tmpLabels.erase( iter );
1392 for( QStringList::Iterator iter = m_addedLabels.begin(); iter != m_addedLabels.end(); ++iter )
1394 if( tmpLabels.indexOf( *iter ) == tmpLabels.indexOf( *tmpLabels.end() ) )
1395 tmpLabels.append( *iter );
1397 storeLabels( *it, tmpLabels );
1401 QStringList
1402 TagDialog::labelListFromText( const QString &text )
1404 QStringList tmp = text.split(',', QString::SkipEmptyParts);
1405 //insert each string into a map to remove duplicates
1406 QMap<QString, int> map;
1407 oldForeach( tmp )
1409 QString tmpString = (*it).trimmed();
1410 if ( !tmpString.isEmpty() ) {
1411 map.remove( tmpString );
1412 map.insert( tmpString, 0 );
1415 QStringList result;
1416 QMap<QString, int>::ConstIterator endMap( map.end() );
1417 for(QMap<QString, int>::ConstIterator it = map.begin(); it != endMap; ++it ) {
1418 result.append( it.key() );
1420 return result;
1423 void
1424 TagDialog::generateDeltaForLabelList( const QStringList &list )
1426 m_addedLabels.clear();
1427 m_removedLabels.clear();
1428 oldForeach( list )
1430 if ( !m_labels.contains( *it ) )
1431 m_addedLabels.append( *it );
1433 oldForeach( m_labels )
1435 if ( !list.contains( *it ) )
1436 m_removedLabels.append( *it );
1438 m_labels = list;
1441 QString
1442 TagDialog::generateHTML( const QStringList &labels )
1444 //the first column of each row is the label name, the second the number of assigned songs
1445 //loop through it to find the highest number of songs, can be removed if somebody figures out a better sql query
1446 QMap<QString, QPair<QString, int> > mapping;
1447 QStringList sortedLabels;
1448 int max = 1;
1449 oldForeach( labels )
1451 QString label = *it;
1452 sortedLabels << label.toLower();
1453 ++it;
1454 int value = ( *it ).toInt();
1455 if ( value > max )
1456 max = value;
1457 mapping[label.toLower()] = QPair<QString, int>( label, value );
1459 sortedLabels.sort();
1460 QString html = "<html><body>";
1461 oldForeach( sortedLabels )
1463 QString key = *it;
1464 //generate a number in the range 1..10 based on how much the label is used
1465 int labelUse = ( mapping[key].second * 10 ) / max;
1466 if ( labelUse == 0 )
1467 labelUse = 1;
1468 html.append( QString( "<span class='label size%1'><a href=\"label:%2\">%3</a></span> " )
1469 .arg( QString::number( labelUse ), mapping[key].first, mapping[key].first ) );
1471 html.append( "</html></body>" );
1472 debug() << "Dumping HTML for label cloud: " << html;
1473 return html;
1476 void
1477 TagDialog::openUrlRequest(const KUrl &url ) //SLOT
1479 DEBUG_BLOCK
1480 if ( url.protocol() == "label" )
1482 QString text = kTextEdit_selectedLabels->toPlainText();
1483 QStringList currentLabels = labelListFromText( text );
1484 if ( currentLabels.contains( url.path() ) )
1485 return;
1486 if ( !text.isEmpty() )
1487 text.append( ", " );
1488 text.append( url.path() );
1489 kTextEdit_selectedLabels->setText( text );
1493 bool
1494 TagDialog::writeTag( MetaBundle &mb, bool updateCB )
1496 QByteArray path = QFile::encodeName( mb.url().path() );
1497 if ( !TagLib::File::isWritable( path ) ) {
1498 Amarok::StatusBar::instance()->longMessage( i18n(
1499 "The file %1 is not writable.", mb.url().fileName() ), KDE::StatusBar::Error );
1500 return false;
1503 //visual feedback
1504 QApplication::setOverrideCursor( Qt::WaitCursor );
1506 bool result = mb.save();
1507 mb.updateFilesize();
1509 if( result )
1510 //update the collection db
1511 CollectionDB::instance()->updateTags( mb.url().path(), mb, updateCB );
1513 QApplication::restoreOverrideCursor();
1515 return result;
1518 TagDialogWriter::TagDialogWriter( const QMap<QString, MetaBundle> tagsToChange )
1519 : ThreadManager::Job( "TagDialogWriter" ),
1520 m_successCount ( 0 ),
1521 m_failCount ( 0 )
1523 QApplication::setOverrideCursor( Qt::WaitCursor );
1524 QMap<QString, MetaBundle>::ConstIterator end = tagsToChange.end();
1525 for(QMap<QString, MetaBundle>::ConstIterator it = tagsToChange.begin(); it != end; ++it ) {
1526 MetaBundle mb = it.value();
1527 m_tags += mb;
1531 bool
1532 TagDialogWriter::doJob()
1534 for( int i = 0, size=m_tags.size(); i<size; ++i ) {
1535 QByteArray path = QFile::encodeName( m_tags[i].url().path() );
1536 if ( !TagLib::File::isWritable( path ) ) {
1537 Amarok::StatusBar::instance()->longMessageThreadSafe( i18n(
1538 "The file %1 is not writable.", m_tags[i].url().fileName() ), KDE::StatusBar::Error );
1539 m_failed += true;
1540 continue;
1543 bool result = m_tags[i].save();
1544 m_tags[i].updateFilesize();
1546 if( result )
1547 m_successCount++;
1548 else {
1549 m_failCount++;
1550 m_failedURLs += m_tags[i].prettyUrl();
1552 m_failed += !result;
1554 return true;
1557 void
1558 TagDialogWriter::completeJob()
1560 for( int i = 0, size=m_tags.size(); i<size; ++i ) {
1561 if ( !m_failed[i] ) {
1562 CollectionDB::instance()->updateTags( m_tags[i].url().path(), m_tags[i], false /* don't update browsers*/ );
1563 Playlist::instance()->updateMetaData( m_tags[i] );
1566 QApplication::restoreOverrideCursor();
1567 //PORT 2.0 if ( m_successCount )
1568 //PORT 2.0 CollectionView::instance()->databaseChanged();
1569 if ( m_failCount )
1570 Amarok::StatusBar::instance()->longMessage( i18n(
1571 "Sorry, the tag for the following files could not be changed:\n", m_failedURLs.join( ";\n" ) ), KDE::StatusBar::Error );
1575 #include "tagdialog.moc"