Commit 28cde8f5 authored by Zack Rusin's avatar Zack Rusin

MusicBrainz support. The initial version (just detecting file tags is supported,

submissions are not yet there).

svn path=/trunk/kdemultimedia/juk/; revision=224495
parent d706bd02
......@@ -140,7 +140,12 @@ void JuK::setupActions()
// tagger menu
new KAction(i18n("&Save"), "filesave", "CTRL+t", m_splitter, SLOT(slotSaveTag()), actionCollection(), "saveItem");
new KAction(i18n("&Delete"), "editdelete", 0, m_splitter, SLOT(slotDeleteSelectedItems()), actionCollection(), "removeItem");
new KAction(i18n("&Guess Tag Information"), 0, "CTRL+g", m_splitter, SLOT(slotGuessTagInfo()), actionCollection(), "guessTag");
KActionMenu *guessMenu = new KActionMenu(i18n("&Guess Tag Information"), "", actionCollection(), "guessTag");
guessMenu->insert(new KAction(i18n("From &File"), 0, "CTRL+g", m_splitter,
SLOT(slotGuessTagInfoFile()), actionCollection(), "guessTagFile"));
guessMenu->insert(new KAction(i18n("From &Internet"), 0, "CTRL+i", m_splitter,
SLOT(slotGuessTagInfoInternet()), actionCollection(), "guessTagInternet"));
//new KAction(i18n("&Rename File"), 0, "CTRL+r", m_splitter, SLOT(slotRenameFile()), actionCollection(), "renameFile");
// settings menu
......@@ -790,5 +795,5 @@ void JuK::openFile(const QString &file)
{
m_splitter->open(file);
}
#include "juk.moc"
......@@ -231,7 +231,7 @@ void Playlist::saveAs()
// If there's no playlist name set, use the file name.
if(m_playlistName.isEmpty())
emit signalNameChanged(name());
save();
}
}
......@@ -462,12 +462,23 @@ void Playlist::slotRenameFile()
KApplication::restoreOverrideCursor();
}
void Playlist::slotGuessTagInfo()
void Playlist::slotGuessTagInfoFile()
{
KApplication::setOverrideCursor(Qt::waitCursor);
PlaylistItemList items = selectedItems();
for(PlaylistItemList::Iterator it = items.begin(); it != items.end(); ++it)
(*it)->guessTagInfoFromFile();
KApplication::restoreOverrideCursor();
}
void Playlist::slotGuessTagInfoInternet()
{
//not sure if the cursor stuff makes sense
//since guessing will be asynchronous anyway
KApplication::setOverrideCursor(Qt::waitCursor);
PlaylistItemList items = selectedItems();
for(PlaylistItemList::Iterator it = items.begin(); it != items.end(); ++it)
(*it)->guessTagInfo();
(*it)->guessTagInfoFromInternet();
KApplication::restoreOverrideCursor();
}
......@@ -772,7 +783,7 @@ void Playlist::setup()
// hide some columns by default
//////////////////////////////////////////////////
hideColumn(PlaylistItem::CommentColumn);
hideColumn(PlaylistItem::FileNameColumn);
......@@ -965,7 +976,7 @@ void Playlist::applyTag(QListViewItem *item, const QString &text, int column)
void Playlist::slotApplyModification(QListViewItem *item, const QString &text, int column)
{
// kdDebug(65432) << "Playlist::slotApplyModification()" << endl;
if(text == m_editText)
return;
......
......@@ -128,7 +128,7 @@ public:
int count() const { return childCount(); }
/**
/**
* This gets the next item to be played.
*/
PlaylistItem *nextItem(PlaylistItem *current, bool random = false);
......@@ -163,7 +163,8 @@ public slots:
virtual void clear();
virtual void selectAll() { KListView::selectAll(true); }
void slotGuessTagInfo();
void slotGuessTagInfoFile();
void slotGuessTagInfoInternet();
void slotRenameFile();
/**
......@@ -257,9 +258,9 @@ private:
QPtrList<PlaylistItem> m_history;
QString m_fileName;
/**
* Used to store the text for inline editing before it is changed so that
* Used to store the text for inline editing before it is changed so that
* we can know if something actually changed and as such if we need to save
* the tag.
*/
......
......@@ -51,8 +51,8 @@ const Tag *PlaylistItem::tag() const
// isn't defined yet
QString PlaylistItem::fileName() const
{
return m_data->fileInfo()->fileName();
{
return m_data->fileInfo()->fileName();
}
QString PlaylistItem::filePath() const
......@@ -70,12 +70,12 @@ QString PlaylistItem::dirPath(bool absPath) const
return m_data->fileInfo()->dirPath(absPath);
}
bool PlaylistItem::isWritable() const
bool PlaylistItem::isWritable() const
{
return m_data->fileInfo()->isWritable();
}
void PlaylistItem::guessTagInfo()
void PlaylistItem::guessTagInfoFromFile()
{
TagGuesser guesser(tag()->absFilePath());
......@@ -94,6 +94,17 @@ void PlaylistItem::guessTagInfo()
slotRefresh();
}
void PlaylistItem::guessTagInfoFromInternet()
{
#ifdef HAVE_MUSICBRAINZ
MusicBrainzQuery *query = new MusicBrainzQuery(MusicBrainzQuery::File,
tag()->absFilePath());
connect(query, SIGNAL(signalDone(const MusicBrainzQuery::TrackList &)),
SLOT(slotTagGuessResults(const MusicBrainzQuery::TrackList &)));
query->start();
#endif //add message box teeling users musicbrainz is not installed or keep it quiet?
}
void PlaylistItem::renameFile()
{
FileRenamer renamer;
......@@ -122,14 +133,14 @@ void PlaylistItem::slotRefreshFromDisk()
// PlaylistItem protected methods
////////////////////////////////////////////////////////////////////////////////
PlaylistItem::PlaylistItem(CollectionListItem *item, Playlist *parent) :
PlaylistItem::PlaylistItem(CollectionListItem *item, Playlist *parent) :
QObject(parent), KListViewItem(parent),
m_playing(false)
{
setup(item, parent);
}
PlaylistItem::PlaylistItem(CollectionListItem *item, Playlist *parent, QListViewItem *after) :
PlaylistItem::PlaylistItem(CollectionListItem *item, Playlist *parent, QListViewItem *after) :
QObject(parent), KListViewItem(parent, after),
m_playing(false)
{
......@@ -138,7 +149,7 @@ PlaylistItem::PlaylistItem(CollectionListItem *item, Playlist *parent, QListView
// This constructor should only be used by the CollectionList subclass.
PlaylistItem::PlaylistItem(CollectionList *parent) :
PlaylistItem::PlaylistItem(CollectionList *parent) :
QObject(parent), KListViewItem(parent),
m_collectionItem(static_cast<CollectionListItem *>(this)), m_data(0), m_playing(false)
{
......@@ -149,9 +160,9 @@ void PlaylistItem::paintCell(QPainter *p, const QColorGroup &cg, int column, int
{
if(!m_playing)
return KListViewItem::paintCell(p, cg, column, width, align);
QColorGroup colorGroup = cg;
QColor base = colorGroup.base();
QColor selection = colorGroup.highlight();
......@@ -200,9 +211,9 @@ int PlaylistItem::compare(QListViewItem *item, int column, bool ascending) const
int PlaylistItem::compare(const PlaylistItem *firstItem, const PlaylistItem *secondItem, int column, bool ascending) const
{
// Try some very basic caching for "two in a row" searches. From what I've
// Try some very basic caching for "two in a row" searches. From what I've
// seen this is ~15% of all calls.
static const PlaylistItem *previousFirstItem = 0;
static const PlaylistItem *previousSecondItem = 0;
static int previousColumn = 0;
......@@ -214,7 +225,7 @@ int PlaylistItem::compare(const PlaylistItem *firstItem, const PlaylistItem *sec
previousFirstItem = firstItem;
previousSecondItem = secondItem;
previousColumn = column;
if(column == TrackNumberColumn) {
if(firstItem->tag()->trackNumber() > secondItem->tag()->trackNumber()) {
previousResult = 1;
......@@ -250,7 +261,7 @@ int PlaylistItem::compare(const PlaylistItem *firstItem, const PlaylistItem *sec
}
bool PlaylistItem::isValid() const
{
{
return m_data && m_data->tag();
}
......@@ -261,8 +272,8 @@ bool PlaylistItem::isValid() const
void PlaylistItem::slotRefreshImpl()
{
// This should be the only function that needs to be rewritten if the structure of
// PlaylistItemData changes.
// This should be the only function that needs to be rewritten if the structure of
// PlaylistItemData changes.
setText(TrackColumn, tag()->track());
setText(ArtistColumn, tag()->artist());
......@@ -272,14 +283,36 @@ void PlaylistItem::slotRefreshImpl()
setText(YearColumn, tag()->yearString());
setText(LengthColumn, tag()->lengthString());
setText(FileNameColumn, filePath());
QString shortComment = tag()->comment().simplifyWhiteSpace();
if(shortComment.length() > 50)
shortComment = shortComment.left(47) + "...";
setText(CommentColumn, shortComment);
}
#ifdef HAVE_MUSICBRAINZ
void PlaylistItem::slotTagGuessResults(const MusicBrainzQuery::TrackList &res)
{
//FIXME:GUI to pick one of the results
if(res.count() == 0)
return;
MusicBrainzQuery::Track track = res.first();
if(!track.name.isEmpty())
tag()->setTrack(track.name);
if(!track.artist.isEmpty())
tag()->setArtist(track.artist);
if(!track.album.isEmpty())
tag()->setAlbum(track.album);
if(!track.number)
tag()->setTrackNumber(track.number);
tag()->save();
slotRefresh();
}
#endif
////////////////////////////////////////////////////////////////////////////////
// PlaylistItem private methods
////////////////////////////////////////////////////////////////////////////////
......
......@@ -18,12 +18,15 @@
#ifndef PLAYLISTITEM_H
#define PLAYLISTITEM_H
#include <config.h>
#include <klistview.h>
#include <qfileinfo.h>
#include <qobject.h>
#include <qptrstack.h>
#include "musicbrainzquery.h"
#include "tag.h"
#include "cache.h"
......@@ -76,7 +79,8 @@ public:
bool isWritable() const;
void setPlaying(bool playing = true) { m_playing = playing; }
void guessTagInfo();
void guessTagInfoFromFile();
void guessTagInfoFromInternet();
void renameFile();
public slots:
......@@ -129,6 +133,9 @@ protected:
protected slots:
void slotRefreshImpl();
#ifdef HAVE_MUSICBRAINZ
void slotTagGuessResults(const MusicBrainzQuery::TrackList &);
#endif
signals:
void signalRefreshed();
......
......@@ -44,9 +44,9 @@ void processEvents()
// public methods
////////////////////////////////////////////////////////////////////////////////
PlaylistSplitter::PlaylistSplitter(QWidget *parent, bool restore, const char *name) :
PlaylistSplitter::PlaylistSplitter(QWidget *parent, bool restore, const char *name) :
QSplitter(Qt::Horizontal, parent, name),
m_playingItem(0), m_searchWidget(0), m_dynamicList(0), m_restore(restore),
m_playingItem(0), m_searchWidget(0), m_dynamicList(0), m_restore(restore),
m_nextPlaylistItem(0)
{
setupLayout();
......@@ -70,20 +70,20 @@ QString PlaylistSplitter::uniquePlaylistName(const QString &startingWith, bool u
int playlistNumber = 1;
// while the list contains more than zero instances of the generated
// while the list contains more than zero instances of the generated
// string...
if(useParenthesis) {
while(names.contains(startingWith + " (" + QString::number(playlistNumber) + ")") != 0)
playlistNumber++;
return startingWith + " (" + QString::number(playlistNumber) + ")";
return startingWith + " (" + QString::number(playlistNumber) + ")";
}
else
{
while(names.contains(startingWith + ' ' + QString::number(playlistNumber)) != 0)
playlistNumber++;
return startingWith + " " + QString::number(playlistNumber);
}
}
......@@ -93,7 +93,7 @@ QString PlaylistSplitter::playNextFile(bool random, bool loopPlaylist)
PlaylistItem *i;
// Four basic cases here: (1) We've asked for a specific next item, (2) play
// the item that's after the currently playing item, (3) play the selected
// the item that's after the currently playing item, (3) play the selected
// item or (4) play the first item in the list.
// (1) we've asked for a specific next item
......@@ -208,16 +208,16 @@ QString PlaylistSplitter::playingList() const
return QString::null;
}
void PlaylistSplitter::open(const QString &file)
void PlaylistSplitter::open(const QString &file)
{
if(file.isEmpty())
return;
if(visiblePlaylist() == m_collection ||
KMessageBox::questionYesNo(this,
i18n("Do you want to add this item to the current list or to the collection list?"),
QString::null,
KGuiItem(i18n("Current")),
if(visiblePlaylist() == m_collection ||
KMessageBox::questionYesNo(this,
i18n("Do you want to add this item to the current list or to the collection list?"),
QString::null,
KGuiItem(i18n("Current")),
KGuiItem(i18n("Collection"))) == KMessageBox::No)
{
slotAddToPlaylist(file, m_collection);
......@@ -226,17 +226,17 @@ void PlaylistSplitter::open(const QString &file)
slotAddToPlaylist(file, visiblePlaylist());
}
void PlaylistSplitter::open(const QStringList &files)
void PlaylistSplitter::open(const QStringList &files)
{
if(files.isEmpty())
return;
if(visiblePlaylist() == m_collection ||
KMessageBox::questionYesNo(this,
i18n("Do you want to add these items to the current list or to the collection list?"),
QString::null,
KGuiItem(i18n("Current")),
KGuiItem(i18n("Collection"))) == KMessageBox::No)
if(visiblePlaylist() == m_collection ||
KMessageBox::questionYesNo(this,
i18n("Do you want to add these items to the current list or to the collection list?"),
QString::null,
KGuiItem(i18n("Current")),
KGuiItem(i18n("Collection"))) == KMessageBox::No)
{
slotAddToPlaylist(files, m_collection);
}
......@@ -261,22 +261,22 @@ void PlaylistSplitter::slotOpen()
}
void PlaylistSplitter::slotOpenDirectory()
{
{
DirectoryList *l = new DirectoryList(m_directoryList, this, "directoryList");
m_directoryQueue.clear();
m_directoryQueueRemove.clear();
connect(l, SIGNAL(signalDirectoryAdded(const QString &)),
connect(l, SIGNAL(signalDirectoryAdded(const QString &)),
this, SLOT(slotQueueDirectory(const QString &)));
connect(l, SIGNAL(signalDirectoryRemoved(const QString &)),
connect(l, SIGNAL(signalDirectoryRemoved(const QString &)),
this, SLOT(slotQueueDirectoryRemove(const QString &)));
if(l->exec() == QDialog::Accepted) {
open(m_directoryQueue);
for(QStringList::Iterator it = m_directoryQueue.begin(); it != m_directoryQueue.end(); it++)
m_dirWatch->addDir(*it, false, true);
m_directoryList += m_directoryQueue;
QStringList::Iterator it = m_directoryQueueRemove.begin();
......@@ -293,7 +293,7 @@ Playlist *PlaylistSplitter::slotCreatePlaylist()
// If this text is changed, please also change it in PlaylistBox::duplicate().
QString name = KLineEditDlg::getText(i18n("Create New Playlist"),
QString name = KLineEditDlg::getText(i18n("Create New Playlist"),
i18n("Please enter a name for the new playlist:"),
uniquePlaylistName(), &ok);
if(ok)
......@@ -308,7 +308,7 @@ void PlaylistSplitter::slotSelectPlaying()
return;
Playlist *l = static_cast<Playlist *>(m_playingItem->listView());
if(!l)
return;
......@@ -348,9 +348,16 @@ void PlaylistSplitter::slotAddToPlaylist(const QStringList &files, Playlist *lis
m_editor->slotUpdateCollection();
}
void PlaylistSplitter::slotGuessTagInfo()
void PlaylistSplitter::slotGuessTagInfoFile()
{
visiblePlaylist()->slotGuessTagInfo();
visiblePlaylist()->slotGuessTagInfoFile();
if(m_editor)
m_editor->slotRefresh();
}
void PlaylistSplitter::slotGuessTagInfoInternet()
{
visiblePlaylist()->slotGuessTagInfoInternet();
if(m_editor)
m_editor->slotRefresh();
}
......@@ -377,7 +384,7 @@ void PlaylistSplitter::setupLayout()
QSplitter *editorSplitter = new QSplitter(Qt::Vertical, this, "editorSplitter");
// Create the playlist and the editor.
m_playlistStack = new QWidgetStack(editorSplitter, "playlistStack");
m_editor = new TagEditor(editorSplitter, "tagEditor");
......@@ -385,24 +392,24 @@ void PlaylistSplitter::setupLayout()
editorSplitter->setResizeMode(m_editor, QSplitter::FollowSizeHint);
// Make the connection that will update the selected playlist when a
// Make the connection that will update the selected playlist when a
// selection is made in the playlist box.
connect(m_playlistBox, SIGNAL(signalCurrentChanged(const PlaylistList &)),
connect(m_playlistBox, SIGNAL(signalCurrentChanged(const PlaylistList &)),
this, SLOT(slotChangePlaylist(const PlaylistList &)));
connect(m_playlistBox, SIGNAL(signalDoubleClicked()), this, SIGNAL(signalListBoxDoubleClicked()));
// Create the collection list; this should always exist. This has a
// Create the collection list; this should always exist. This has a
// slightly different creation process than normal playlists (since it in
// fact is a subclass) so it is created here rather than by using
// fact is a subclass) so it is created here rather than by using
// slotCreatePlaylist().
CollectionList::initialize(m_playlistStack, m_restore);
m_collection = CollectionList::instance();
setupPlaylist(m_collection, true, "folder_sound");
connect(m_collection, SIGNAL(signalCollectionChanged()), m_editor, SLOT(slotUpdateCollection()));
connect(m_collection, SIGNAL(signalRequestPlaylistCreation(const PlaylistItemList &)),
connect(m_collection, SIGNAL(signalRequestPlaylistCreation(const PlaylistItemList &)),
this, SLOT(slotCreatePlaylist(const PlaylistItemList &)));
......@@ -430,7 +437,7 @@ void PlaylistSplitter::readConfig()
splitterSizes.append(640);
}
setSizes(splitterSizes);
if(m_restore) {
QString playlistsFile = KGlobal::dirs()->saveLocation("appdata") + "playlists";
......@@ -456,7 +463,7 @@ void PlaylistSplitter::readConfig()
QTimer::singleShot(0, this, SLOT(slotScanDirectories()));
m_dirWatch = new KDirWatch();
connect(m_dirWatch, SIGNAL(dirty(const QString &)),
connect(m_dirWatch, SIGNAL(dirty(const QString &)),
this, SLOT(slotDirChanged(const QString &)));
QStringList::Iterator it = m_directoryList.begin();
......@@ -488,7 +495,7 @@ void PlaylistSplitter::saveConfig()
QString playlistsFile = KGlobal::dirs()->saveLocation("appdata") + "playlists";
QFile f(playlistsFile);
if(f.open(IO_WriteOnly)) {
QDataStream s(&f);
......@@ -538,9 +545,9 @@ void PlaylistSplitter::setupPlaylist(Playlist *p, bool raise, const char *icon)
this, SIGNAL(signalDoubleClicked()));
connect(p, SIGNAL(signalNumberOfItemsChanged(Playlist *)),
this, SLOT(slotPlaylistCountChanged(Playlist *)));
connect(p, SIGNAL(signalAboutToRemove(PlaylistItem *)),
connect(p, SIGNAL(signalAboutToRemove(PlaylistItem *)),
this, SLOT(slotPlaylistItemRemoved(PlaylistItem *)));
connect(p, SIGNAL(signalFilesDropped(const QStringList &, Playlist *)),
connect(p, SIGNAL(signalFilesDropped(const QStringList &, Playlist *)),
this, SLOT(slotAddToPlaylist(const QStringList &, Playlist *)));
connect(p, SIGNAL(signalSetNext(PlaylistItem *)),
this, SLOT(slotSetNextItem(PlaylistItem *)));
......@@ -555,9 +562,9 @@ void PlaylistSplitter::setupPlaylist(Playlist *p, bool raise, const char *icon)
Playlist *PlaylistSplitter::openPlaylist(const QString &file)
{
QFileInfo fileInfo(file);
if(!fileInfo.exists() ||
!fileInfo.isFile() ||
!fileInfo.isReadable() ||
if(!fileInfo.exists() ||
!fileInfo.isFile() ||
!fileInfo.isReadable() ||
m_playlistFiles.insert(fileInfo.absFilePath()))
{
return 0;
......@@ -658,7 +665,7 @@ void PlaylistSplitter::slotShowSearchResults(const QString &query, bool caseSens
PlaylistSearch search(playlists, components);
Playlist::setItemsVisible(search.matchedItems(), true);
Playlist::setItemsVisible(search.matchedItems(), true);
Playlist::setItemsVisible(search.unmatchedItems(), false);
}
......
......@@ -37,9 +37,9 @@ class PlaylistItem;
/**
* This is the main layout class of JuK. It should contain a PlaylistBox and
* a QWidgetStack of the Playlists.
* a QWidgetStack of the Playlists.
*
* This class serves as a "mediator" (see "Design Patterns") between the JuK
* This class serves as a "mediator" (see "Design Patterns") between the JuK
* class and the playlist classes. Thus all access to the playlist classes from
* non-Playlist related classes should be through the public API of this class.
*/
......@@ -60,11 +60,11 @@ public:
*/
QString uniquePlaylistName(const QString &startingWith, bool useParentheses = false);
/* This calls the above method with startingWith == i18n("Playlist") to
/* This calls the above method with startingWith == i18n("Playlist") to
* produce "Playlist 1", "Playlist 2", ...
*/
QString uniquePlaylistName() { return uniquePlaylistName(i18n("Playlist")); }
////////////////////////////////////////////////////////////////////////////
// Variations on the theme "play stuff"
////////////////////////////////////////////////////////////////////////////
......@@ -74,7 +74,7 @@ public:
* file.
*/
QString playNextFile(bool random = false, bool loopPlaylist = false);
/**
* Returns the file name of the previous item and moves the playing indicator
* to the previous file.
......@@ -87,7 +87,7 @@ public:
void populatePlayHistoryMenu(QPopupMenu* menu, bool random);
/**
* Returns the name of the currently selected file and moves the playing
* Returns the name of the currently selected file and moves the playing
* indicator to that file.
*/
QString playSelectedFile();
......@@ -105,7 +105,7 @@ public:
QString playRandomFile();
/**
* Since the player is handled at a higher level, this just clears the
* Since the player is handled at a higher level, this just clears the
* pointer to the currently playing item and updates the icon.
*/
void stop();
......@@ -143,7 +143,7 @@ public:
void open(const QString &file);
QStringList columnNames() const { return m_columnNames; }
KActionMenu *columnVisibleAction() const { return m_collection->columnVisibleAction(); }
/**
......@@ -169,7 +169,8 @@ public slots:
// Tagger slots
void slotSaveTag() { m_editor->save(); }
void slotGuessTagInfo();
void slotGuessTagInfoFile();
void slotGuessTagInfoInternet();
void slotRenameFile();
// Playlist slots
......@@ -186,12 +187,12 @@ public slots:
void slotSelectPlaying();
// Other slots
/**
* Deletes the selected items from the hard disk.
* Deletes the selected items from the hard disk.
*/
void slotDeleteSelectedItems();
/**
* Refresh the contents of the currently visible playlist. This will cause
* all of the audio meta data to be reread from disk.
......@@ -209,7 +210,7 @@ public slots:
* Add the file to the playlist.
*/
void slotAddToPlaylist(const QString &file, Playlist *list);
/**
* Adds the files to the playlist.
*/
......@@ -231,7 +232,7 @@ signals:
private:
/**
* Returns a PlaylistItemList of the selected PlaylistItems in the top playlist in
* Returns a PlaylistItemList of the selected PlaylistItems in the top playlist in
* the QWidgetStack of playlists.
*/
PlaylistItemList playlistSelection() const { return visiblePlaylist()->selectedItems(); }
......@@ -265,19 +266,19 @@ private:
* and then returns the name of the file.
*/
QString play(PlaylistItem *item);
private slots:
void slotChangePlaylist(const PlaylistList &l);
void slotPlaylistCountChanged(Playlist *p);
/**
* Add a directory to the directory list queue. We need to queue these
* Add a directory to the directory list queue. We need to queue these
* rather than processing them when they become available because the user
* could cancel the action.
*/
void slotQueueDirectory(const QString &directory) { m_directoryQueue.append(directory); }
/**