boombox: import version 0.4.4

This commit is contained in:
Ivailo Monev 2015-09-25 03:01:49 +00:00
parent 9b717eb5eb
commit 9a34fd6a9e
53 changed files with 7475 additions and 0 deletions

20
boombox/CMakeLists.txt Normal file
View file

@ -0,0 +1,20 @@
project(boombox)
find_package(KDE4 REQUIRED)
include (KDE4Defaults)
find_package(Sqlite REQUIRED)
find_package(Taglib REQUIRED)
include_directories(${SQLITE_INCLUDE_DIR} ${KDE4_INCLUDES} ${QT_INCLUDES} ${TAGLIB_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(WARNING "enabling debug output!")
add_definitions(-DDEBUG)
else()
add_definitions(-DQT_NO_DEBUG_OUTPUT)
endif()
add_subdirectory( src )
add_subdirectory( icons )

View file

@ -0,0 +1 @@
kde4_install_icons( ${ICON_INSTALL_DIR} )

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

View file

@ -0,0 +1,47 @@
set(boombox_SRCS
bbfilechangejob.cpp
bbresultview.cpp
bbfilterdock.cpp
bbsetmodel.cpp
bbmetadata.cpp
bbdatabase.cpp
threadjob.cpp
dbupdatejob.cpp
dbqueryjob.cpp
bbmetainfodialog.cpp
bbmainwindow.cpp
bbplaylistsystem.cpp
bbcollectiontab.cpp
bbfilesystemtab.cpp
bbstreamstab.cpp
bbstreameditform.cpp
mpris2player.cpp
main.cpp
directoriespage.ui
bbmetainfodialog.ui
bbstreameditform.ui
)
qt4_add_dbus_adaptor(boombox_SRCS dbus/org.mpris.MediaPlayer2.xml
mpris2player.h Mpris2Player)
qt4_add_dbus_adaptor(boombox_SRCS dbus/org.mpris.MediaPlayer2.Player.xml
mpris2player.h Mpris2Player)
qt4_add_dbus_interface(boombox_SRCS dbus/org.mpris.MediaPlayer2.Player.xml
mpris2playerclient)
kde4_add_kcfg_files(boombox_SRCS bbsettings.kcfgc)
add_executable(boombox ${boombox_SRCS})
target_link_libraries(boombox ${KDE4_KDEUI_LIBS} ${KDE4_PHONON_LIBS}
${KDE4_THREADWEAVER_LIBS} ${KDE4_KFILE_LIBS}
${SQLITE_LIBRARIES} ${TAGLIB_LIBRARIES})
add_definitions(${TAGLIB_CFLAGS} ${SQLITE_DEFINITIONS})
########### install files ###############
install(TARGETS boombox ${INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES boombox.desktop DESTINATION ${XDG_APPS_INSTALL_DIR})
install(FILES boombox.kcfg DESTINATION ${KCFG_INSTALL_DIR})

View file

@ -0,0 +1,584 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "bbcollectiontab.h"
#include "bbmainwindow.h"
#include "bbsettings.h"
#include "dbupdatejob.h"
#include "bbsetmodel.h"
#include "dbqueryjob.h"
#include "bbfilterdock.h"
#include "bbresultview.h"
#include "bbsetmodel.h"
#include "bbfilechangejob.h"
#include <QComboBox>
#include <QDir>
#include <QTimer>
#include <QLayout>
#include <QEvent>
#include <QLabel>
#include <KLocale>
#include <KAction>
#include <KActionCollection>
#include <KStandardAction>
#include <kuiserverjobtracker.h>
#include <KGlobal>
#include <KInputDialog>
#include <KMenu>
#include <KMessageBox>
#include <KToolBar>
#include <kuiserverjobtracker.h>
#include <threadweaver/ThreadWeaver.h>
#include <threadweaver/JobCollection.h>
using namespace ThreadWeaver;
BBCollectionTab::BBCollectionTab(int pTabNumber)
: BBPlaylistSystem(pTabNumber)
{
setWindowTitle(i18n("Music Collection"));
mActionCollection = new KActionCollection(this);
setupActions();
setupToolBars();
setDockNestingEnabled(true);
BBFilterDock *lFilterDock = new BBFilterDock("artist", i18n("Artists"), this);
mActionCollection->addAction("show_artist", lFilterDock->toggleViewAction());
addDockWidget(Qt::LeftDockWidgetArea, lFilterDock);
mDocks.append(lFilterDock);
BBFilterDock *lFilterDock2 = new BBFilterDock("album", i18n("Albums"), this);
mActionCollection->addAction("show_album", lFilterDock2->toggleViewAction());
splitDockWidget(lFilterDock, lFilterDock2, Qt::Horizontal);
mDocks.append(lFilterDock2);
lFilterDock = new BBFilterDock("genre", i18n("Genres"), this);
mActionCollection->addAction("show_genre", lFilterDock->toggleViewAction());
addDockWidget(Qt::RightDockWidgetArea, lFilterDock);
mDocks.append(lFilterDock);
lFilterDock = new BBFilterDock("year", i18n("Years"), this);
mActionCollection->addAction("show_year", lFilterDock->toggleViewAction());
addDockWidget(Qt::RightDockWidgetArea, lFilterDock);
lFilterDock->hide();
mDocks.append(lFilterDock);
mModel = new BBAlbumSongModel(this, this);
mResultView = new BBResultView(this, mModel);
setCentralWidget(mResultView);
qRegisterMetaType<QSet<BBStringListItem> >("QSet<BBStringListItem>");
qRegisterMetaType<QSet<BBSongListItem> >("QSet<BBSongListItem>");
qRegisterMetaType<QSet<BBAlbumListItem> >("QSet<BBAlbumListItem>");
setAutoSaveSettings("CollectionTab");
mCurrentSong = 0;
}
void BBCollectionTab::setupActions()
{
KStandardAction::openNew(this, SLOT(createNewPlaylist()), mActionCollection);
KStandardAction::save(this, SLOT(savePlaylist()), mActionCollection);
KAction *lAction = new KAction(KIcon("view-refresh"), i18n("Update database"), this);
lAction->setShortcut(KShortcut(Qt::CTRL + Qt::Key_U));
mActionCollection->addAction("update_database", lAction);
connect(lAction, SIGNAL(triggered()), SLOT(updateDatabase()));
lAction = new KAction(KIcon("edit-delete"), i18n("Delete"), this);
mActionCollection->addAction("delete_playlist", lAction);
connect(lAction, SIGNAL(triggered()), SLOT(deletePlaylist()));
}
void BBCollectionTab::setupToolBars()
{
mPlaylistToolBar = toolBar("playlist_toolbar");
mPlaylistToolBar->setWindowTitle(i18n("Playlist Toolbar"));
mPlaylistToolBar->addAction(mActionCollection->action(KStandardAction::name(KStandardAction::New)));
mPlaylistComboBox = new QComboBox(this);
mPlaylistComboBox->setInsertPolicy(QComboBox::InsertAlphabetically);
mPlaylistComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
mPlaylistComboBox->setMinimumWidth(mPlaylistComboBox->sizeHint().width()*2);
mPlaylistToolBar->addWidget(mPlaylistComboBox);
mPlaylistToolBar->addAction(mActionCollection->action(KStandardAction::name(KStandardAction::Save)));
mControlToolBar = toolBar("control_toolbar");
mControlToolBar->setWindowTitle(i18n("Playback Control Toolbar"));
mControlToolBar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea);
mControlsContainer = new QWidget(this);
mControlsLayout = new QHBoxLayout(mControlsContainer);
mControlToolBar->addWidget(mControlsContainer);
}
void BBCollectionTab::embedControls(QWidget *pControls)
{
pControls->setParent(mControlsContainer);
mControlsLayout->addWidget(pControls);
pControls->show();
}
BBTabMenuBar *BBCollectionTab::getTabMenuBar()
{
BBTabMenuBar *lDataMenuBar = new BBTabMenuBar();
KMenu *lDataMenu = new KMenu(i18n("Playlist"));
lDataMenu->addAction(mActionCollection->action(KStandardAction::name(KStandardAction::New)));
lDataMenu->addAction(mActionCollection->action(KStandardAction::name(KStandardAction::Save)));
lDataMenu->addAction(mActionCollection->action("delete_playlist"));
lDataMenu->addSeparator();
lDataMenu->addAction(mActionCollection->action("update_database"));
lDataMenuBar->addMenu(lDataMenu);
KMenu *lViewMenu = new KMenu(i18n("View"));
foreach(BBFilterDock *i, mDocks)
{
lViewMenu->addAction(mActionCollection->action(QString("show_%1").arg(i->category())));
}
lDataMenuBar->addMenu(lViewMenu);
return lDataMenuBar;
}
void BBCollectionTab::createNewPlaylist()
{
QString lNewName = KInputDialog::getText(i18nc("dialog caption", "New Playlist"),
i18n("Please provide a name for the new playlist:"),
i18nc("default playlist name", "New Playlist"));
if(lNewName.isNull())
return;
if(mPlaylistComboBox->findText(lNewName) != -1)
{
KMessageBox::sorry(this, i18n("The playlist \"%1\" already exists, please choose another name.").arg(lNewName));
return;
}
savePlaylist(lNewName);
mPlaylistComboBox->disconnect(SIGNAL(currentIndexChanged(QString)));
mPlaylistComboBox->addItem(lNewName);
mPlaylistComboBox->setCurrentIndex(mPlaylistComboBox->findText(lNewName));
connect(mPlaylistComboBox, SIGNAL(currentIndexChanged(QString)), SLOT(loadPlaylist(QString)));
KConfigGroup lListGroup(mConfig, "PlaylistSystem");
QStringList lListNames;
for(int i = 0; i < mPlaylistComboBox->count(); ++i)
lListNames.append(mPlaylistComboBox->itemText(i));
lListGroup.writeEntry("ListNames", lListNames);
mConfig->sync();
}
void BBCollectionTab::savePlaylist()
{
savePlaylist(mPlaylistComboBox->currentText());
}
void BBCollectionTab::deletePlaylist()
{
if(mPlaylistComboBox->count() == 1)
return;
KConfigGroup lGroup(mConfig, QString("__playlist__") + mPlaylistComboBox->currentText());
lGroup.deleteGroup();
mPlaylistComboBox->removeItem(mPlaylistComboBox->currentIndex());
KConfigGroup lListGroup(mConfig, "PlaylistSystem");
QStringList lListNames;
for(int i = 0; i < mPlaylistComboBox->count(); ++i)
lListNames.append(mPlaylistComboBox->itemText(i));
lListGroup.writeEntry("ListNames", lListNames);
mConfig->sync();
}
void BBCollectionTab::savePlaylist(const QString &pName)
{
KConfigGroup lGroup(mConfig, QString("__playlist__") + pName);
foreach(BBFilterDock *i, mDocks)
{
lGroup.writeEntry(i->category(), i->currentSelection());
lGroup.writeEntry(i->category() + "_filter", i->filterText());
}
mConfig->sync();
}
void BBCollectionTab::updateDatabase()
{
DBUpdateJob *lJob = new DBUpdateJob();
mJobTracker->registerJob(lJob);
connect(lJob, SIGNAL(result(KJob*)), SLOT(databaseUpdated(KJob*)));
lJob->start();
}
void BBCollectionTab::databaseUpdated(KJob *pJob)
{
if(!pJob->error())
{
BBSettings::setDatabaseUpdateTime(QDateTime::currentDateTime());
BBSettings::self()->writeConfig();
}
}
void BBCollectionTab::queryNextSong(BBSongQueryJob &pJob)
{
QModelIndex lCurrentSongIndex = mModel->indexForFileID(mCurrentSong);
if(lCurrentSongIndex.isValid())
{
QPersistentModelIndex lIndex;
if(pJob.mShuffle)
lIndex = mModel->nextShuffled(lCurrentSongIndex);
else
lIndex = mModel->nextLinear(lCurrentSongIndex);
if(lIndex.isValid())
{
pJob.mSong = lIndex.data(BBFileIDRole);
emit songQueryReady(pJob);
return;
}
}
if(pJob.mShuffle)
pJob.mSong = mModel->firstShuffled().data(BBFileIDRole);
else
pJob.mSong = mModel->firstLinear().data(BBFileIDRole);
emit songQueryReady(pJob);
}
void BBCollectionTab::queryPreviousSong(BBSongQueryJob &pJob)
{
QModelIndex lCurrentSongIndex = mModel->indexForFileID(mCurrentSong);
if(lCurrentSongIndex.isValid())
{
QPersistentModelIndex lIndex;
if(pJob.mShuffle)
lIndex = mModel->prevShuffled(lCurrentSongIndex);
else
lIndex = mModel->prevLinear(lCurrentSongIndex);
if(lIndex.isValid())
{
pJob.mSong = lIndex.data(BBFileIDRole);
emit songQueryReady(pJob);
return;
}
}
if(pJob.mShuffle)
pJob.mSong = mModel->lastShuffled().data(BBFileIDRole);
else
pJob.mSong = mModel->lastLinear().data(BBFileIDRole);
emit songQueryReady(pJob);
}
void BBCollectionTab::setCurrentSong(const QVariant &pSong)
{
int lNewSong = pSong.toInt();
QModelIndex lNewSongIndex= mModel->indexForFileID(lNewSong);
QModelIndex lCurrentSongIndex= mModel->indexForFileID(mCurrentSong);
int lNewAlbum;
if(lNewSongIndex.isValid())
lNewAlbum = lNewSongIndex.data(BBAlbumIDRole).toInt();
else
{
BBResultTable lResult;
if(mDatabase.getTable(QString::fromAscii("SELECT album_ID FROM songs WHERE file_ID = %1").arg(lNewSong),
lResult) && lResult.mNumRows == 1)
{
lNewAlbum = QString(lResult.at(1, 0)).toInt();
mDatabase.freeTable(lResult);
}
else
lNewAlbum = -1;
}
mModel->setCurrentSong(lNewSong, lNewAlbum, lCurrentSongIndex);
mResultView->scrollToNewSong(lNewSongIndex, lCurrentSongIndex);
mCurrentSong = lNewSong;
}
QVariant BBCollectionTab::currentSong()
{
return QVariant::fromValue(mCurrentSong);
}
QString BBCollectionTab::displayString(const QVariant &pSong)
{
int lSong = pSong.toInt();
QModelIndex lSongIndex = mModel->indexForFileID(lSong);
if(lSongIndex.isValid())
return QString("%1 - %2").arg(lSongIndex.data(BBArtistRole).toString(),
lSongIndex.data(BBTitleRole).toString());
else
{
BBResultTable lResult;
if(mDatabase.getTable(QString::fromAscii("SELECT artist, title FROM songs WHERE file_ID = %1").arg(lSong),
lResult) && lResult.mNumRows == 1)
{
QString lDisplay = QString("%1 - %2").arg(lResult.at(1, 0), lResult.at(1, 1));
mDatabase.freeTable(lResult);
return lDisplay;
}
else
return QString();
}
}
KUrl BBCollectionTab::songUrl(const QVariant &pSong)
{
int lSong = pSong.toInt();
QModelIndex lSongIndex = mModel->indexForFileID(lSong);
if(lSongIndex.isValid())
return KUrl(QString("%1/%2").arg(lSongIndex.data(BBPathRole).toString(),
lSongIndex.data(BBFileNameRole).toString()));
else
{
BBResultTable lResult;
if(mDatabase.getTable(QString::fromAscii("SELECT path, file_name FROM songs JOIN albums USING (album_ID) "
"WHERE file_ID = %1").arg(lSong), lResult) && lResult.mNumRows == 1)
{
QString lUrl = QString("%1/%2").arg(lResult.at(1, 0), lResult.at(1, 1));
mDatabase.freeTable(lResult);
return KUrl(lUrl);
}
else
return KUrl();
}
}
JobCollection *BBCollectionTab::getRefreshJob(BBFilterDock *pNotThisOne)
{
JobCollection *lJobCollection = new JobCollection();
connect(lJobCollection, SIGNAL(done(ThreadWeaver::Job *)), SLOT(deleteJob(ThreadWeaver::Job *)));
Job *lJob = new SongQueryJob(getSongsCondition());
lJob->assignQueuePolicy(mDBPool);
lJobCollection->addJob(lJob);
connect(lJob, SIGNAL(resultReady(QSet<BBSongListItem>, QSet<BBAlbumListItem>)),
mModel, SLOT(setNewSongs(QSet<BBSongListItem>,QSet<BBAlbumListItem>)));
connect(lJob, SIGNAL(done(ThreadWeaver::Job *)), SLOT(deleteJob(ThreadWeaver::Job *)));
foreach(BBFilterDock *lCurrent, mDocks)
{
if(lCurrent != pNotThisOne)
{
Job *lJob = new FilterQueryJob(lCurrent->category(), getQueryFor(lCurrent));
lJob->assignQueuePolicy(mDBPool);
lJobCollection->addJob(lJob);
connect(lJob, SIGNAL(resultReady(QSet<BBStringListItem>)),
lCurrent, SLOT(processResult(QSet<BBStringListItem>)));
connect(lJob, SIGNAL(done(ThreadWeaver::Job *)), SLOT(deleteJob(ThreadWeaver::Job *)));
}
}
return lJobCollection;
}
void BBCollectionTab::refreshAllExcept(BBFilterDock *pNotThisOne)
{
if(mRefreshInhibited)
return;
Weaver::instance()->requestAbort();
Weaver::instance()->dequeue();
Weaver::instance()->enqueue(getRefreshJob(pNotThisOne));
}
QString BBCollectionTab::getQueryFor(BBFilterDock *pSubject)
{
QStringList lTerms;
BBFilterDock *lCurrent;
foreach(lCurrent, mDocks)
{
lCurrent->addCondition(lCurrent != pSubject, lTerms);
}
return lTerms.join(" AND ");
}
QString BBCollectionTab::getSongsCondition()
{
QString lQuery1, lQuery = getQueryFor(NULL);
QString lFilter = BBDatabase::prepareString(mResultView->filterText());
if(!lFilter.isEmpty())
lQuery1 = QString("(artist LIKE '%%1%' OR album LIKE '%%1%' OR title LIKE '%%1%')").arg(lFilter);
if(!lQuery1.isEmpty())
{
if(lQuery.isEmpty())
{
lQuery.append(lQuery1);
}
else
{
lQuery.append(" AND ");
lQuery.append(lQuery1);
}
}
return lQuery;
}
void BBCollectionTab::deleteJob(ThreadWeaver::Job *pJob)
{
pJob->deleteLater();
}
void BBCollectionTab::changeFiles(const QString &pField, const QString &pOldValue)
{
QSet<BBSongListItem> lFileSet = mResultView->filesToUpdate();
QString lCaption = i18ncp("dialog title", "Change Tag", "Change Tags", lFileSet.count());
QString lLabel = i18np("Change the %2 field of one file to:", "Change the %2 field of %1 files to:",
lFileSet.count(), i18nc("file tag type", pField.toLocal8Bit()));
QString lNewValue = KInputDialog::getText(lCaption, lLabel, pOldValue, 0, this);
if(lNewValue.isNull())
return;
//set songs to be removed and added again in the refresh triggered later.
mModel->updateSongs(lFileSet);
QList<int> lFileIDList;
QSetIterator<BBSongListItem> lIterator(lFileSet);
while(lIterator.hasNext())
lFileIDList.append(lIterator.next().mData.mFileID);
BBFileChangeJob *lJob = new BBFileChangeJob(lFileIDList, pField, lNewValue);
mJobTracker->registerJob(lJob);
connect(lJob, SIGNAL(result(KJob*)), SLOT(fileChangeJobDone(KJob*)));
lJob->start();
}
void BBCollectionTab::fileChangeJobDone(KJob *pJob)
{
BBFileChangeJob *lJob = qobject_cast<BBFileChangeJob *>(pJob);
mRefreshInhibited = true;
foreach(BBFilterDock *i, mDocks)
{
if(i->category() == lJob->mField)
{
i->clearSelection();
i->clearFilterText();
QStringList lList(lJob->mNewValue);
i->forceSelection(lList);
break;
}
}
mRefreshInhibited = false;
JobCollection *lRefreshJob = getRefreshJob(NULL);
connect(lRefreshJob, SIGNAL(done(ThreadWeaver::Job*)), SLOT(finishLoadingPlaylist()));
Weaver::instance()->enqueue(lRefreshJob);
}
void BBCollectionTab::resetAllFilters()
{
mRefreshInhibited = true;
foreach(BBFilterDock *i, mDocks)
{
i->clearFilterText();
i->clearSelection();
}
mRefreshInhibited = false;
refreshAllExcept(NULL);
}
void BBCollectionTab::loadPlaylist(const QString &pName)
{
if(mRefreshInhibited)
return;
mPlaylistName = pName;
KSharedConfigPtr lConfig = KGlobal::config();
KConfigGroup lGroup(lConfig, QString("__playlist__") + mPlaylistName);
mRefreshInhibited = true;
foreach(BBFilterDock *i, mDocks)
{
i->clearFilterText();
i->clearSelection();
i->forceSelection(lGroup.readEntry(i->category(), QStringList()));
i->setFilterText(lGroup.readEntry(i->category() + "_filter", QString()));
}
mRefreshInhibited = false;
JobCollection *lRefreshJob = getRefreshJob(NULL);
connect(lRefreshJob, SIGNAL(done(ThreadWeaver::Job*)), SLOT(finishLoadingPlaylist()));
Weaver::instance()->enqueue(lRefreshJob);
}
void BBCollectionTab::finishLoadingPlaylist()
{
mRefreshInhibited = true;
foreach(BBFilterDock *i, mDocks)
{
i->syncSelection();
}
mRefreshInhibited = false;
mModel->updateSongs(QSet<BBSongListItem>());
}
void BBCollectionTab::saveSession(KConfigGroup &pConfigGroup)
{
pConfigGroup.writeEntry("chosen_playlist", mPlaylistComboBox->currentIndex());
foreach(BBFilterDock *i, mDocks)
{
pConfigGroup.writeEntry(i->category(), i->currentSelection());
pConfigGroup.writeEntry(i->category() + "_filter", i->filterText());
}
}
void BBCollectionTab::readSession(KConfigGroup &pConfigGroup)
{
mJobTracker = new KUiServerJobTracker(this);
Weaver::instance()->setMaximumNumberOfThreads(QThread::idealThreadCount());
mDBPool = new DBPool(QThread::idealThreadCount());
mDatabase.connect(BBSettings::fileNameDB());
mConfig = KGlobal::config();
KConfigGroup lListGroup(mConfig, "PlaylistSystem");
QStringList lDefaultPlaylistNames;
lDefaultPlaylistNames << i18n("Default Playlist");
mPlaylistComboBox->addItems(lListGroup.readEntry("ListNames", lDefaultPlaylistNames));
connect(mPlaylistComboBox, SIGNAL(currentIndexChanged(QString)), SLOT(loadPlaylist(QString)));
mRefreshInhibited = true;
mPlaylistComboBox->setCurrentIndex(pConfigGroup.readEntry("chosen_playlist", 0));
foreach(BBFilterDock *i, mDocks)
{
i->clearFilterText();
i->clearSelection();
i->forceSelection(pConfigGroup.readEntry(i->category(), QStringList()));
i->setFilterText(pConfigGroup.readEntry(i->category() + "_filter", QString()));
}
mRefreshInhibited = false;
JobCollection *lRefreshJob = getRefreshJob(NULL);
connect(lRefreshJob, SIGNAL(done(ThreadWeaver::Job*)), SLOT(finishLoadingPlaylist()));
Weaver::instance()->enqueue(lRefreshJob);
}
void BBCollectionTab::refreshAllViewsAndUpdateItem(QModelIndex pIndex)
{
QSet<BBSongListItem> lSongToUpdate;
lSongToUpdate.insert(mModel->findSongItem(pIndex));
mModel->updateSongs(lSongToUpdate);
JobCollection *lRefreshJob = getRefreshJob(NULL);
connect(lRefreshJob, SIGNAL(done(ThreadWeaver::Job*)), SLOT(finishLoadingPlaylist()));
Weaver::instance()->enqueue(lRefreshJob);
}

View file

@ -0,0 +1,126 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef BBCOLLECTIONTAB_H
#define BBCOLLECTIONTAB_H
#include "bbdatabase.h"
#include "bbplaylistsystem.h"
#include <QList>
#include <QPersistentModelIndex>
#include <QSet>
#include <KSharedConfig>
class QHBoxLayout;
class QComboBox;
class KUiServerJobTracker;
class KActionCollection;
class KJob;
class KToolBar;
class BBPlaylistSystem;
class BBTabMenuBar;
class DBPool;
class BBFilterDock;
class BBResultView;
class BBAlbumSongModel;
class BBSongListItem;
class BBAlbumListItem;
Q_DECLARE_METATYPE(QPersistentModelIndex)
namespace ThreadWeaver
{
class Job;
class JobCollection;
}
class BBCollectionTab : public BBPlaylistSystem
{
Q_OBJECT
public:
explicit BBCollectionTab(int pTabNumber);
BBTabMenuBar *getTabMenuBar();
virtual void queryNextSong(BBSongQueryJob &pJob);
virtual void queryPreviousSong(BBSongQueryJob &pJob);
virtual QVariant currentSong();
virtual void setCurrentSong(const QVariant &pSong);
virtual QString displayString(const QVariant &pSong);
virtual KUrl songUrl(const QVariant &pSong);
virtual void readSession(KConfigGroup &pConfigGroup);
virtual void saveSession(KConfigGroup &pConfigGroup);
virtual void embedControls(QWidget *pControls);
void refreshAllExcept(BBFilterDock *pSubject);
void changeFiles(const QString &pField, const QString &pOldValue);
KActionCollection *actionCollection()
{
return mActionCollection;
}
public slots:
void savePlaylist(const QString &pName);
void loadPlaylist(const QString &pName);
void refreshAllViewsAndUpdateItem(QModelIndex pIndex);
protected slots:
void deleteJob(ThreadWeaver::Job *pJob);
void fileChangeJobDone(KJob *pJob);
void resetAllFilters();
void finishLoadingPlaylist();
void createNewPlaylist();
void savePlaylist();
void updateDatabase();
void databaseUpdated(KJob *);
void deletePlaylist();
protected:
void setupActions();
void setupToolBars();
ThreadWeaver::JobCollection *getRefreshJob(BBFilterDock *pNotThisOne);
QString getQueryFor(BBFilterDock *pSubject);
QString getSongsCondition();
DBPool *mDBPool;
QList<BBFilterDock *> mDocks;
BBResultView *mResultView;
BBAlbumSongModel *mModel;
int mCurrentSong;
bool mRefreshInhibited;
QString mPlaylistName;
QComboBox *mPlaylistComboBox;
KToolBar *mControlToolBar;
KToolBar *mPlaylistToolBar;
QWidget *mControlsContainer;
QHBoxLayout *mControlsLayout;
KUiServerJobTracker *mJobTracker;
BBPlaylistSystem *mPlaylistSystem;
KSharedConfigPtr mConfig;
KActionCollection *mActionCollection;
BBDatabase mDatabase;
};
#endif

120
boombox/src/bbdatabase.cpp Normal file
View file

@ -0,0 +1,120 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "bbdatabase.h"
#include "bbmetadata.h"
#include <QDir>
#include <sqlite3.h>
BBDatabase::BBDatabase()
: mConnected(false), mSqlite(NULL)
{
}
BBDatabase::~BBDatabase()
{
if(mConnected)
disconnect();
}
bool BBDatabase::connect(const QString &pFileName)
{
if(mConnected)
disconnect();
int lError = sqlite3_open(pFileName.toUtf8(), &mSqlite);
if(lError != SQLITE_OK)
{
qCritical("error opening: %i", lError);
return false;
}
executeStatement("CREATE TABLE IF NOT EXISTS albums (album_ID INTEGER PRIMARY KEY, path STRING, "
"album STRING, is_VA INTEGER, cover_art_path STRING)");
executeStatement("CREATE TABLE IF NOT EXISTS songs (file_ID INTEGER PRIMARY KEY, "
"album_ID INTEGER, file_name STRING, "
"artist STRING, title STRING, track INTEGER, year INTEGER, genre STRING, "
"comment STRING, length STRING)");
executeStatement("CREATE VIEW IF NOT EXISTS dead_album_view AS SELECT * FROM albums "
"LEFT OUTER JOIN songs USING (album_ID) WHERE file_name IS NULL");
executeStatement("CREATE TRIGGER IF NOT EXISTS delete_album_trigger INSTEAD OF DELETE ON dead_album_view BEGIN "
"DELETE FROM albums WHERE albums.album_ID = old.album_ID; END");
mConnected = true;
return true;
}
void BBDatabase::disconnect()
{
if(mConnected)
{
mConnected = false;
sqlite3_close(mSqlite);
mSqlite = NULL;
}
}
bool BBDatabase::executeStatement(const QString &pStatement)
{
char *lError = NULL;
int lReturnCode;
qDebug("Executing statement: %s", (const char *)pStatement.toUtf8());
lReturnCode = sqlite3_exec(mSqlite, pStatement.toUtf8(), NULL, NULL, &lError);
if(lError != NULL)
{
qWarning("database error: %s", lError);
sqlite3_free(lError);
}
return lReturnCode == SQLITE_OK;
}
bool BBDatabase::getTable(const QString &pQuery, BBResultTable &pTable)
{
char *lError = NULL;
int lReturnCode;
qDebug("Executing statement: %s", (const char *)pQuery.toUtf8());
lReturnCode = sqlite3_get_table(mSqlite, pQuery.toUtf8(), &pTable.mTable, &pTable.mNumRows, &pTable.mNumCols, &lError);
if(lError != NULL)
{
qWarning("database error: %s", lError);
sqlite3_free(lError);
}
qDebug("Number of rows in result: %i", pTable.mNumRows);
return lReturnCode == SQLITE_OK;
}
void BBDatabase::freeTable(BBResultTable &pTable)
{
sqlite3_free_table(pTable.mTable);
pTable.mTable = NULL;
pTable.mNumRows = 0;
pTable.mNumCols = 0;
}
QString BBDatabase::prepareList(const QStringList &pList)
{
if(pList.isEmpty())
return QString();
QStringList lTmp;
QStringListIterator i(pList);
while(i.hasNext())
lTmp << prepareString(i.next());
return "(\'" + lTmp.join("\', \'") + "\')";
}

71
boombox/src/bbdatabase.h Normal file
View file

@ -0,0 +1,71 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef BBDATABASE_H
#define BBDATABASE_H
#include <QObject>
#include <QList>
#include <QSet>
class BBMetaData;
class BBStringListItem;
class BBDataListItem;
struct sqlite3;
class BBResultTable
{
public:
int mNumRows;
int mNumCols;
char **mTable;
char *at(int pRow, int pColumn)
{
return mTable[pRow*mNumCols + pColumn];
}
};
class BBDatabase
{
public:
BBDatabase();
~BBDatabase();
bool connect(const QString &pFileName);
void disconnect();
bool isConnected(){return mConnected;}
sqlite3 *getDatabase(){return mSqlite;}
bool executeStatement(const QString &pStatement);
bool getTable(const QString &pQuery, BBResultTable &pTable);
static void freeTable(BBResultTable &pTable);
void insertLine(BBMetaData &pMetaData);
static QString prepareString(const QString &pStr)
{
return QString(pStr).replace('\'', "''");
}
static QString prepareList(const QStringList &pList);
protected:
bool mConnected;
sqlite3 *mSqlite;
};
#endif

View file

@ -0,0 +1,105 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "bbfilechangejob.h"
#include "bbmetadata.h"
#include "bbsettings.h"
#include <KLocale>
#include <QFileInfo>
#include <sqlite3.h>
BBFileChangeJob::BBFileChangeJob(QList<int> pFiles, const QString &pField, const QString &pNewValue)
: mFiles(pFiles), mField(pField), mNewValue(pNewValue)
{
setCapabilities(Killable | Suspendable);
}
void BBFileChangeJob::doWork()
{
mDatabase.connect(BBSettings::fileNameDB());
setTotalAmount(Files, mFiles.count());
int lFileCount = 0;
foreach(int lFileID, mFiles)
{
if(checkForDeathOrSuspend())
return;
BBResultTable lResult;
mDatabase.getTable(QString("SELECT path || '/' || file_name FROM albums JOIN songs USING (album_ID) "
"WHERE file_ID = '%1'").arg(lFileID), lResult);
QString lPath = QString::fromUtf8(lResult.at(1, 0));
mDatabase.freeTable(lResult);
emit description(this, i18n("Writing change to files"), qMakePair(i18n("Path"), lPath));
QFileInfo lInfo(lPath);
if(!lPath.contains("://"))
BBMetaData::ChangeFile(lPath, mField, mNewValue);
if(mField == "album")
{
int lAlbumID;
mDatabase.getTable(QString(
"SELECT DISTINCT album_ID FROM songs JOIN albums USING (album_ID) "
"WHERE album = '%1' AND path = '%2'").arg(BBDatabase::prepareString(mNewValue),
BBDatabase::prepareString(lInfo.absolutePath())),
lResult);
if(lResult.mNumRows == 0)
{
mDatabase.executeStatement(QString(
"INSERT INTO albums (path, album, is_VA, cover_art_path) "
"VALUES ('%1','%2','%3','%4')").arg(BBDatabase::prepareString(lInfo.absolutePath()),
BBDatabase::prepareString(mNewValue),
QString::number(0), QString())
);
lAlbumID = sqlite3_last_insert_rowid(mDatabase.getDatabase());
}
else
{
lAlbumID = QString::fromUtf8(lResult.at(1, 0)).toInt();
}
mDatabase.freeTable(lResult);
mDatabase.executeStatement(
QString("UPDATE songs SET album_ID = %1 WHERE file_ID = %2").arg(lAlbumID).arg(lFileID)
);
}
else
{
mDatabase.executeStatement(
QString("UPDATE songs SET %1 = '%2' WHERE file_ID = '%3'").arg(mField, BBDatabase::prepareString(mNewValue)).arg(lFileID)
);
}
lFileCount++;
setProcessedAmount(Files, lFileCount);
setPercent((ulong)(100*processedAmount(Files))/totalAmount(Files));
}
mDatabase.executeStatement("DELETE FROM dead_album_view");
mDatabase.executeStatement("UPDATE albums SET is_VA = 1 WHERE album_ID IN "
"(SELECT album_ID FROM songs JOIN albums USING (album_ID) "
"GROUP BY album_ID HAVING count(DISTINCT artist) > 1)");
mDatabase.executeStatement("UPDATE albums SET is_VA = 0 WHERE album_ID IN "
"(SELECT album_ID FROM songs JOIN albums USING (album_ID) "
"GROUP BY album_ID HAVING count(DISTINCT artist) = 1)");
emitResult();
}

View file

@ -0,0 +1,43 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef BBFILECHANGEJOB_H
#define BBFILECHANGEJOB_H
#include <QStringList>
#include "threadjob.h"
#include "bbdatabase.h"
class BBFileChangeJob : public ThreadJob
{
Q_OBJECT
public:
BBFileChangeJob(QList<int> pFiles, const QString &pField, const QString &pNewValue);
QList<int> mFiles;
QString mField;
QString mNewValue;
BBDatabase mDatabase;
public slots:
void doWork();
};
#endif // BBFILECHANGEJOB_H

View file

@ -0,0 +1,307 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "bbfilesystemtab.h"
#include "bbmainwindow.h"
#include "bbmetadata.h"
#include <QDockWidget>
#include <QFileSystemModel>
#include <QLayout>
#include <QMetaType>
#include <KActionCollection>
#include <KActionMenu>
#include <KCategorizedSortFilterProxyModel>
#include <KConfigGroup>
#include <KDirModel>
#include <KFilePlacesModel>
#include <KFilePlacesView>
#include <KGlobal>
#include <KGlobalSettings>
#include <KLocale>
#include <KMenu>
#include <KToolBar>
#include <KUrlNavigator>
#include <QDebug>
BBFileSystemTab::BBFileSystemTab(int pTabNumber)
:BBPlaylistSystem(pTabNumber)
{
setWindowTitle(i18n("File System"));
}
void BBFileSystemTab::queryNextSong(BBSongQueryJob &pJob)
{
mSongQuery = pJob;
mSearchStack.clear();
mSearchingForward = true;
buildSearchStack();
findSongInSearchStack();
}
void BBFileSystemTab::queryPreviousSong(BBSongQueryJob &pJob)
{
mSongQuery = pJob;
mSearchStack.clear();
mSearchingForward = false;
buildSearchStack();
findSongInSearchStack();
}
void BBFileSystemTab::buildSearchStack()
{
QModelIndex lCurrent;
if(!mCurrentSong.isNull())
lCurrent = mProxyModel->mapFromSource(mModel->indexForItem(mCurrentSong));
if(lCurrent.isValid())
{
if(mSearchingForward)
{
do
{
int lStartRow = lCurrent.row() + 1;
lCurrent = lCurrent.parent();
int lMax = mProxyModel->rowCount(lCurrent);
for(int i = lStartRow; i < lMax; ++i)
mSearchStack.append(mModel->itemForIndex(mProxyModel->mapToSource(mProxyModel->index(i, 0, lCurrent))));
}while(lCurrent.isValid());
}
else
{
do
{
int lStartRow = lCurrent.row() - 1;
lCurrent = lCurrent.parent();
for(int i = lStartRow; i >= 0; --i)
mSearchStack.append(mModel->itemForIndex(mProxyModel->mapToSource(mProxyModel->index(i, 0, lCurrent))));
}while(lCurrent.isValid());
}
}
else
{
if(mSearchingForward)
{
int lMax = mProxyModel->rowCount();
for(int i = 0; i < lMax; ++i)
mSearchStack.append(mModel->itemForIndex(mProxyModel->mapToSource(mProxyModel->index(i, 0))));
}
else
{
for(int i = mProxyModel->rowCount() - 1; i >= 0; --i)
mSearchStack.append(mModel->itemForIndex(mProxyModel->mapToSource(mProxyModel->index(i, 0))));
}
}
}
void BBFileSystemTab::findSongInSearchStack()
{
while(!mSearchStack.isEmpty())
{
KFileItem lCurrent = mSearchStack.takeFirst();
if((lCurrent.isFile() || lCurrent.isLink()) && lCurrent.isReadable())
{
mSongQuery.mSong = lCurrent;
emit songQueryReady(mSongQuery);
mSearchStack.clear();
return;
}
if(lCurrent.isDir() && lCurrent.isReadable())
{
QModelIndex lCurrentIndex = mModel->indexForItem(lCurrent);
if(!lCurrentIndex.isValid())
continue;
if(mModel->canFetchMore(lCurrentIndex))
{
mModel->fetchMore(lCurrentIndex);
mSearchStack.prepend(lCurrent);
return;
}
QModelIndex lCurrentProxyIndex = mProxyModel->mapFromSource(lCurrentIndex);
if(mSearchingForward)
{
for(int i = mProxyModel->rowCount(lCurrentProxyIndex) - 1; i >= 0; --i)
mSearchStack.prepend(mModel->itemForIndex(mProxyModel->mapToSource(mProxyModel->index(i, 0, lCurrentProxyIndex))));
}
else
{
for(int i = 0; i < mProxyModel->rowCount(lCurrentProxyIndex); ++i)
mSearchStack.prepend(mModel->itemForIndex(mProxyModel->mapToSource(mProxyModel->index(i, 0, lCurrentProxyIndex))));
}
}
}
}
QVariant BBFileSystemTab::currentSong()
{
return mCurrentSong;
}
void BBFileSystemTab::setCurrentSong(const QVariant &pSong)
{
mCurrentSong = pSong.value<KFileItem>();
KUrl lUrl(mCurrentSong.url().directory());
QString lDirectory = lUrl.url();
QString lDirectory2 = mDirOperator->url().url();
if(!lDirectory.startsWith(lDirectory2))
{
mDirOperator->setUrl(lDirectory, true);
mFilePlacesView->setUrl(lDirectory);
mUrlNavigator->setLocationUrl(lDirectory);
}
mDirOperator->setCurrentItem(mCurrentSong);
}
QString BBFileSystemTab::displayString(const QVariant &pSong)
{
KFileItem lFileItem = pSong.value<KFileItem>();
if(!lFileItem.isLocalFile())
return QString();
BBMetaData lMetaData;
QString lPath = lFileItem.localPath();
QString lFileName = lFileItem.name();
lPath.chop(lFileName.length());
lMetaData.GetInfoFrom(lPath, lFileName);
return QString("%1 - %2").arg(lMetaData.mArtist, lMetaData.mTitle);
}
KUrl BBFileSystemTab::songUrl(const QVariant &pSong)
{
KFileItem lSong = pSong.value<KFileItem>();
if(lSong.isNull())
return KUrl();
else
return lSong.targetUrl();
}
void BBFileSystemTab::readSession(KConfigGroup &pConfigGroup)
{
mFilePlacesModel = new KFilePlacesModel(this);
mCenterWidget = new QWidget(this);
setCentralWidget(mCenterWidget);
mLayout = new QVBoxLayout(mCenterWidget);
mFilePlacesView = new KFilePlacesView();
mFilePlacesView->setModel(mFilePlacesModel);
mDirOperator = new KDirOperator(KGlobalSettings::musicPath(), this);
QString lMimeTypes = QString("application/x-ogg;audio/basic;audio/vnd.rn-realaudio;audio/x-aiff;"
"audio/x-flac;audio/x-matroska;audio/x-mp3;audio/mpeg;audio/ogg;"
"audio/x-flac+ogg;audio/x-vorbis+ogg;audio/x-ms-wma;"
"audio/x-pn-realaudio;audio/x-wav;inode/directory");
QStringList lMimeFilter = lMimeTypes.split(";");
mDirOperator->setMimeFilter(lMimeFilter);
mDirOperator->readConfig(pConfigGroup);
mDirOperator->setView(KFile::DetailTree);
mDirOperator->view()->setAlternatingRowColors(true);
mDirOperator->actionCollection()->action("preview")->setChecked(false);
KAction *lEnqueueAction = new KAction(i18n("Add to Queue"), this);
lEnqueueAction->setShortcut(Qt::CTRL + Qt::Key_E);
lEnqueueAction->setShortcutContext(Qt::WidgetShortcut);
connect(lEnqueueAction, SIGNAL(triggered()), SLOT(enqueueTriggered()));
mDirOperator->view()->addAction(lEnqueueAction);
KMenu *lPopupMenu = new KMenu(this);
lPopupMenu->addAction(lEnqueueAction);
lPopupMenu->addSeparator();
lPopupMenu->addAction(mDirOperator->actionCollection()->action("sorting menu"));
lPopupMenu->addAction(mDirOperator->actionCollection()->action("view menu"));
lPopupMenu->addSeparator();
lPopupMenu->addAction(mDirOperator->actionCollection()->action("properties"));
KActionMenu *lActionMenu = (KActionMenu *)mDirOperator->actionCollection()->action("popupMenu");
lActionMenu->setMenu(lPopupMenu);
mUrlNavigator = new KUrlNavigator(mFilePlacesModel, mDirOperator->url(), this);
connect(mUrlNavigator, SIGNAL(urlChanged(KUrl)), this, SLOT(setOperatorUrl(KUrl)));
connect(mFilePlacesView, SIGNAL(urlChanged(KUrl)),this, SLOT(setOperatorUrl(KUrl)));
connect(mDirOperator, SIGNAL(urlEntered(KUrl)), this, SLOT(setCurrentPath(KUrl)));
connect(mDirOperator, SIGNAL(fileSelected(KFileItem)), this, SLOT(selectFile(KFileItem)));
mLayout->addWidget(mUrlNavigator);
mLayout->addWidget(mDirOperator);
QDockWidget *lPlacesDock = new QDockWidget(i18n("Places"), this);
lPlacesDock->setObjectName("fs_places_dock");
lPlacesDock->setFeatures(QDockWidget::DockWidgetMovable);
lPlacesDock->setWidget(mFilePlacesView);
addDockWidget(Qt::LeftDockWidgetArea, lPlacesDock);
mControlToolBar = toolBar("fs_control_toolbar");
mControlToolBar->setWindowTitle(i18n("Playback Control Toolbar"));
mControlToolBar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea);
mControlsContainer = new QWidget(this);
mControlsLayout = new QHBoxLayout(mControlsContainer);
mControlToolBar->addWidget(mControlsContainer);
setAutoSaveSettings("FileSystemTab");
mProxyModel = (KCategorizedSortFilterProxyModel *)mDirOperator->view()->model();
mModel = (KDirModel *)mProxyModel->sourceModel();
connect(mModel->dirLister(), SIGNAL(completed()), this, SLOT(findSongInSearchStack()));
setCurrentPath(pConfigGroup.readEntry("filesystemtab_path", KGlobalSettings::musicPath()));
}
void BBFileSystemTab::saveSession(KConfigGroup &pConfigGroup)
{
mDirOperator->writeConfig(pConfigGroup);
pConfigGroup.writePathEntry("filesystemtab_path", mDirOperator->url().toEncoded());
}
void BBFileSystemTab::embedControls(QWidget *pControls)
{
pControls->setParent(mControlsContainer);
mControlsLayout->addWidget(pControls);
pControls->show();
}
void BBFileSystemTab::setOperatorUrl(const KUrl &pUrl)
{
mDirOperator->setUrl(pUrl, true);
}
void BBFileSystemTab::selectFile(const KFileItem &pFileItem)
{
if(pFileItem.isFile() && pFileItem.isReadable() && pFileItem != mCurrentSong)
{
gMainWindow->setCurrentPlaylistSystem(tabNumber());
gMainWindow->setCurrentSong(pFileItem);
}
}
void BBFileSystemTab::enqueueTriggered()
{
KFileItemList lSelectedItems = mDirOperator->selectedItems();
foreach(KFileItem lItem, lSelectedItems)
{
if(lItem.isFile() && lItem.isReadable())
gMainWindow->addToPlayQueue(lItem, displayString(lItem), tabNumber());
}
}
void BBFileSystemTab::setCurrentPath(const KUrl &pUrl)
{
mUrlNavigator->setLocationUrl(pUrl);
mFilePlacesView->setUrl(pUrl);
}
void BBFileSystemTab::addManualUrl(const KUrl &pUrl)
{
KFileItem lItem(KFileItem::Unknown, KFileItem::Unknown, pUrl, true);
if(lItem.isFile() && lItem.isReadable())
gMainWindow->addToPlayQueue(lItem, displayString(lItem), tabNumber());
}

View file

@ -0,0 +1,91 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef BBFILESYSTEMTAB_H
#define BBFILESYSTEMTAB_H
#include "bbplaylistsystem.h"
#include <QList>
#include <KDirOperator>
#include <KFileItem>
class QFileSystemModel;
class QHBoxLayout;
class QModelIndex;
class QVBoxLayout;
class KCategorizedSortFilterProxyModel;
class KDirModel;
class KFilePlacesModel;
class KFilePlacesView;
class KToolBar;
class KUrl;
class KUrlNavigator;
class BBFileSystemTab : public BBPlaylistSystem
{
Q_OBJECT
public:
explicit BBFileSystemTab(int pTabNumber);
virtual void queryNextSong(BBSongQueryJob &pJob);
virtual void queryPreviousSong(BBSongQueryJob &pJob);
virtual QVariant currentSong();
virtual void setCurrentSong(const QVariant &pSong);
virtual QString displayString(const QVariant &pSong);
virtual KUrl songUrl(const QVariant &pSong);
void addManualUrl(const KUrl &pUrl);
virtual void readSession(KConfigGroup &pConfigGroup);
virtual void saveSession(KConfigGroup &pConfigGroup);
virtual void embedControls(QWidget *pControls);
KActionCollection *actionCollection()
{
return mDirOperator->actionCollection();
}
protected slots:
void setOperatorUrl(const KUrl &pUrl);
void selectFile(const KFileItem &pFileItem);
void setCurrentPath(const KUrl &pUrl);
void buildSearchStack();
void findSongInSearchStack();
void enqueueTriggered();
protected:
QVBoxLayout *mLayout;
KFilePlacesModel *mFilePlacesModel;
KFilePlacesView *mFilePlacesView;
KUrlNavigator *mUrlNavigator;
KDirOperator *mDirOperator;
QWidget *mCenterWidget;
KToolBar *mControlToolBar;
QWidget *mControlsContainer;
QHBoxLayout *mControlsLayout;
KFileItem mCurrentSong;
KDirModel *mModel;
KCategorizedSortFilterProxyModel *mProxyModel;
QList<KFileItem> mSearchStack;
BBSongQueryJob mSongQuery;
bool mSearchingForward;
};
#endif // BBFILESYSTEMTAB_H

View file

@ -0,0 +1,167 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "bbfilterdock.h"
#include "bbdatabase.h"
#include "bbcollectiontab.h"
#include "bbsetmodel.h"
#include <QApplication>
#include <KLineEdit>
#include <KPushButton>
#include <QListView>
#include <QLayout>
#include <KLocale>
#include <KAction>
#include <QMenu>
BBFilterDock::BBFilterDock(const QString &pCategory, const QString &pTitle, BBCollectionTab *pCollectionTab)
: QDockWidget(pTitle), mCategory(pCategory), mCollectionTab(pCollectionTab)
{
setObjectName(pCategory);
setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable);
QWidget *lWidget = new QWidget(this);
QVBoxLayout *lLayout = new QVBoxLayout;
mLineEdit = new KLineEdit;
mListView = new QListView;
mPushButton = new KPushButton(i18n("Deselect All"));
lLayout->addWidget(mLineEdit);
lLayout->addWidget(mPushButton);
lLayout->addWidget(mListView);
lLayout->setSpacing(1);
lWidget->setLayout(lLayout);
setWidget(lWidget);
mLineEdit->setClearButtonShown(true);
mLineEdit->setClickMessage(i18n("Enter filtering text here..."));
mModel = new BBStringSetModel(this);
mListView->setModel(mModel);
mSelectionModel = mListView->selectionModel();
mListView->setUniformItemSizes(true);
mListView->setSelectionMode(QAbstractItemView::ExtendedSelection);
mListView->setSelectionBehavior(QAbstractItemView::SelectItems);
mListView->setContextMenuPolicy(Qt::CustomContextMenu);
KAction *lEditAction = new KAction(i18n("Edit"), this);
lEditAction->setShortcut(Qt::Key_F2);
lEditAction->setShortcutContext(Qt::WidgetShortcut);
mListView->addAction(lEditAction);
connect(lEditAction, SIGNAL(triggered()), SLOT(editFilesTriggered()));
mContextMenu = new QMenu(this);
mContextMenu->addAction(lEditAction);
connect(mListView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(showPopupMenu(QPoint)));
connect(mSelectionModel, SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
this, SLOT(updateSelection(const QItemSelection &, const QItemSelection &)));
connect(mLineEdit, SIGNAL(textChanged(const QString &)), SLOT(lineEditChanged(const QString &)));
connect(mPushButton, SIGNAL(clicked(bool)), mSelectionModel, SLOT(clearSelection()));
}
void BBFilterDock::lineEditChanged(const QString &pNewText)
{
mFilterTextCondition = QString("%1 LIKE '%%2%'").arg(mCategory, pNewText);
mCollectionTab->refreshAllExcept(NULL);
}
void BBFilterDock::updateSelection(const QItemSelection &pSelected, const QItemSelection &pDeselected)
{
foreach(QModelIndex i, pDeselected.indexes())
{
int lIndex = mSelection.indexOf(i.data().toString());
if(lIndex != -1)
mSelection.removeAt(lIndex);
}
foreach(QModelIndex i, pSelected.indexes())
{
QString lString = i.data().toString();
if(mSelection.indexOf(lString) == -1)
mSelection.append(lString);
}
mQuotedSelection = QString("%1 IN %2").arg(mCategory, BBDatabase::prepareList(mSelection));
mCollectionTab->refreshAllExcept(this);
}
void BBFilterDock::addCondition(bool pUseSelection, QStringList &pList)
{
if(!pUseSelection)
{
if(!mFilterTextCondition.isEmpty())
pList << mFilterTextCondition;
}
else
{
if(!mSelection.isEmpty())
pList << mQuotedSelection;
else
{
if(!mFilterTextCondition.isEmpty())
pList << mFilterTextCondition;
}
}
}
void BBFilterDock::processResult(QSet<BBStringListItem> pResult)
{
mModel->setNewSet(pResult);
QModelIndexList lList = mSelectionModel->selectedIndexes();
if(!lList.isEmpty())
mListView->scrollTo(lList.first());
}
void BBFilterDock::showPopupMenu(const QPoint &pPoint)
{
if(mListView->indexAt(pPoint).isValid())
mContextMenu->popup(mListView->mapToGlobal(pPoint));
}
void BBFilterDock::editFilesTriggered()
{
QModelIndexList lList = mSelectionModel->selectedIndexes();
if(lList.isEmpty())
return; //should never happen...
QString lOldValue = lList.first().data().toString();
mCollectionTab->changeFiles(mCategory, lOldValue);
}
void BBFilterDock::forceSelection(const QStringList &pList)
{
mSelection = pList;
mQuotedSelection = QString("%1 IN %2").arg(mCategory, BBDatabase::prepareList(mSelection));
}
void BBFilterDock::syncSelection()
{
foreach(const QString &lEntry, mSelection)
{
int lIndex = mModel->findStringPos(lEntry);
if(lIndex != -1)
{
mSelectionModel->select(mModel->index(lIndex), QItemSelectionModel::Select);
}
}
QModelIndexList lList = mSelectionModel->selectedIndexes();
if(!lList.isEmpty())
mListView->scrollTo(lList.first());
}

View file

@ -0,0 +1,73 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef BBFILTERDOCK_H
#define BBFILTERDOCK_H
#include <QDockWidget>
#include <QStringList>
#include <QItemSelectionModel>
#include <KLineEdit>
#include "bblistitem.h"
class QListView;
class BBStringSetModel;
class KPushButton;
class BBCollectionTab;
class QMenu;
class BBFilterDock: public QDockWidget
{
Q_OBJECT
public:
BBFilterDock(const QString &pCategory, const QString &pTitle, BBCollectionTab *pCollectionTab);
const QString & category() const {return mCategory;}
void addCondition(bool pUseSelection, QStringList &pList);
QStringList currentSelection() { return mSelection; }
QString filterText() { return mLineEdit->text(); }
public slots:
void clearSelection() { mSelectionModel->clearSelection(); }
void clearFilterText() { mLineEdit->clear(); }
void setFilterText(const QString &pString) { mLineEdit->setText(pString); }
void processResult(QSet<BBStringListItem> pResult);
void updateSelection(const QItemSelection &pSelected, const QItemSelection &pDeselected);
void lineEditChanged(const QString &pNewText);
void forceSelection(const QStringList &pList);
void syncSelection();
protected slots:
void showPopupMenu(const QPoint &pPoint);
void editFilesTriggered();
protected:
QString mCategory;
QString mQuotedSelection;
QString mFilterTextCondition;
QStringList mSelection;
QListView *mListView;
KLineEdit *mLineEdit;
KPushButton *mPushButton;
BBStringSetModel *mModel;
QItemSelectionModel *mSelectionModel;
BBCollectionTab *mCollectionTab;
QMenu *mContextMenu;
};
#endif

139
boombox/src/bblistitem.h Normal file
View file

@ -0,0 +1,139 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef BBLISTITEM_H
#define BBLISTITEM_H
#include <QHash>
#include <QList>
class BBIndex
{
public:
BBIndex()
: mAlbumPos(-1), mSongPos(-1)
{}
BBIndex(int pAlbumPos, int pSongPos)
: mAlbumPos(pAlbumPos), mSongPos(pSongPos)
{}
bool isValid() {return mAlbumPos >= 0 && mSongPos >= 0;}
int mAlbumPos, mSongPos;
};
class BBSongData
{
public:
BBSongData()
: mNextShuffled(), mPrevShuffled()
{
mFileID = -1;
mAlbumID = -1;
}
QString mTitle, mArtist, mLength, mFileName;
int mFileID, mAlbumID;
BBIndex mNextShuffled, mPrevShuffled;
};
class BBAlbumData
{
public:
QString mAlbum, mFolderPath, mCoverArtPath, mArtist;
int mAlbumID;
bool mIsVA;
bool mManuallyExpanded;
QList<BBSongData> mSongs;
};
class BBListItem
{
public:
BBListItem(int pPosition)
: mPosition(pPosition)
{}
BBListItem()
: mPosition(-1)
{}
int mPosition;
};
class BBStringListItem : public BBListItem
{
public:
BBStringListItem(const QString & pString, int pPosition)
: BBListItem(pPosition), mString(pString)
{}
QString mString;
};
class BBSongListItem : public BBListItem
{
public:
BBSongData mData;
BBIndex mIndex;
};
class BBAlbumListItem : public BBListItem
{
public:
BBAlbumData mData;
};
inline bool operator< (const BBListItem &a, const BBListItem &b)
{
return a.mPosition < b.mPosition;
}
inline bool operator> (const BBListItem &a, const BBListItem &b)
{
return a.mPosition > b.mPosition;
}
inline uint qHash(const BBStringListItem &a)
{
return qHash(a.mString);
}
inline bool operator==(const BBStringListItem &a, const BBStringListItem &b)
{
return a.mString == b.mString;
}
inline uint qHash(const BBAlbumListItem &a)
{
return a.mData.mAlbumID;
}
inline bool operator==(const BBAlbumListItem &a, const BBAlbumListItem &b)
{
return a.mData.mAlbumID == b.mData.mAlbumID;
}
inline uint qHash(const BBSongListItem &a)
{
return a.mData.mFileID;
}
inline bool operator==(const BBSongListItem &a, const BBSongListItem &b)
{
return a.mData.mFileID == b.mData.mFileID;
}
#endif

View file

@ -0,0 +1,738 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "bbmainwindow.h"
#include "bbcollectiontab.h"
#include "bbfilesystemtab.h"
#include "bbstreamstab.h"
#include "mpris2player.h"
#include "bbsettings.h"
#include "ui_directoriespage.h"
#include "playeradaptor.h"
#include "mediaplayer2adaptor.h"
#include <QCloseEvent>
#include <QDebug>
#include <QLabel>
#include <QLayout>
#include <QPainter>
#include <QTabBar>
#include <QTimer>
#include <QToolButton>
#include <KMenuBar>
#include <KMenu>
#include <KAboutApplicationDialog>
#include <KApplication>
#include <KAction>
#include <KActionCollection>
#include <KShortcutsDialog>
#include <KStandardAction>
#include <KStandardShortcut>
#include <KConfigDialog>
#include <KMessageBox>
#include <KToolInvocation>
#include <phonon/audiooutput.h>
#include <phonon/seekslider.h>
#include <phonon/mediaobject.h>
#include <phonon/volumeslider.h>
// maximum entries in the playback history
const int gMaxHistory = 10;
class BBCenteredMenuBar : public QWidget
{
public:
BBCenteredMenuBar(QWidget *pParent = 0);
KMenuBar *mMenuBar;
virtual QSize sizeHint() const;
};
BBCenteredMenuBar::BBCenteredMenuBar(QWidget *pParent)
: QWidget(pParent)
{
QHBoxLayout *lLayout = new QHBoxLayout(this);
lLayout->setMargin(0);
mMenuBar = new KMenuBar();
lLayout->addWidget(mMenuBar, 0, Qt::AlignVCenter);
lLayout->addSpacing(14);
}
QSize BBCenteredMenuBar::sizeHint() const
{
return QWidget::sizeHint() + QSize(0, 10); //trick to make oxygen show it correctly.
}
BBTitleLabel::BBTitleLabel(QWidget *pParent)
: QLabel(pParent), mMargin(30), mUpdateInterval(40), mSpeed(0.025)
{
mTimer = new QTimer(this);
connect(mTimer, SIGNAL(timeout()), this, SLOT(advanceAnimation()));
}
void BBTitleLabel::setNewText(const QString &pText)
{
setText(pText);
mVx = -mSpeed;
QRect lRect = contentsRect();
lRect.setWidth(fontMetrics().boundingRect(pText).width() + mMargin);
mSecondPic = QPixmap(lRect.size());
mSecondPic.fill(Qt::transparent);
QPainter lPainter(&mSecondPic);
lPainter.setPen(palette().color(QPalette::Active, QPalette::WindowText));
lPainter.drawText(lRect, Qt::AlignHCenter, text());
if(lRect.width() > width() + mMargin)
{
mX = -mMargin/2.0;
mTimer->start(mUpdateInterval);
}
else
{
mX = (width() - lRect.width())/2.0;
mTimer->stop();
}
}
void BBTitleLabel::advanceAnimation()
{
mX += mVx*mUpdateInterval;
if((mX > 0 && mVx > 0) || (mX + mSecondPic.width() < width() && mVx < 0))
mVx = -mVx;
update();
}
void BBTitleLabel::paintEvent(QPaintEvent *)
{
QPainter lPainter(this);
lPainter.drawPixmap(mX, 0, mSecondPic);
}
void BBTitleLabel::resizeEvent(QResizeEvent *pEvent)
{
QLabel::resizeEvent(pEvent);
if(mSecondPic.width() > width() + mMargin)
mTimer->start(40);
else
{
mX = (width() - mSecondPic.width())/2.0;
mTimer->stop();
}
}
BBMainWindow::BBMainWindow(const KAboutData *pAboutData, QWidget *parent)
: KTabWidget(parent,Qt::Window), mAboutData(pAboutData),
mPlaylistSystem(0), mPlayingPlaylistSystem(0), mLastUpdatedTime(0), mMpris2Player(0)
{
mBufferingStatusTimer = new QTimer(this);
connect(mBufferingStatusTimer, SIGNAL(timeout()), this, SLOT(setBufferingStatus()));
setWindowTitle("BoomBox");
setDocumentMode(true);
setupActions();
setupControls();
connect(tabBar(), SIGNAL(currentChanged(int)), this, SLOT(setActiveTab(int)));
mCollectionTab = new BBCollectionTab(0);
addTab(mCollectionTab, mCollectionTab->windowTitle());
tabBar()->setTabButton(0, QTabBar::RightSide, mCollectionTab->getTabMenuBar());
mFileSystemTab = new BBFileSystemTab(1);
addTab(mFileSystemTab, mFileSystemTab->windowTitle());
mStreamsTab = new BBStreamsTab(2);
addTab(mStreamsTab, mStreamsTab->windowTitle());
mAudioOutput = new Phonon::AudioOutput(Phonon::MusicCategory, this);
mVolumeSlider->setAudioOutput(mAudioOutput);
mMediaObject = new Phonon::MediaObject(this);
mMediaObject->setTickInterval(1000);
connect(mMediaObject, SIGNAL(tick(qint64)), SLOT(updateElapsedTime(qint64)));
connect(mMediaObject, SIGNAL(currentSourceChanged(Phonon::MediaSource)), SLOT(updateCurrentSong(Phonon::MediaSource)));
connect(mMediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
SLOT(updatePhononState(Phonon::State, Phonon::State)));
connect(mMediaObject, SIGNAL(aboutToFinish()), SLOT(queueNextSong()));
connect(mMediaObject, SIGNAL(metaDataChanged()), SLOT(updateMetaDataDisplay()));
mPositionSlider->setMediaObject(mMediaObject);
Phonon::createPath(mMediaObject, mAudioOutput);
}
BBMainWindow::~BBMainWindow()
{
}
void BBMainWindow::setupActions()
{
mActionCollection = new KActionCollection(this);
BBCenteredMenuBar *lLeftMenu = new BBCenteredMenuBar();
KMenu *lBBMenu = new KMenu(i18n("BoomBox"));
lBBMenu->addAction((QAction *)KStandardAction::preferences(this, SLOT(showConfigDialog()), mActionCollection));
lBBMenu->addAction((QAction *)KStandardAction::keyBindings(this, SLOT(showShortcutsDialog()), mActionCollection));
lBBMenu->addSeparator();
lBBMenu->addAction((QAction *)KStandardAction::help(this, SLOT(showHelp()), mActionCollection));
lBBMenu->addAction((QAction *)KStandardAction::aboutApp(this, SLOT(showAboutDialog()), mActionCollection));
lBBMenu->addSeparator();
lBBMenu->addAction((QAction *)KStandardAction::quit(this, SLOT(close()), mActionCollection));
lLeftMenu->mMenuBar->addMenu(lBBMenu);
setCornerWidget(lLeftMenu, Qt::TopLeftCorner);
mPlayPauseAction = new KAction(KIcon("media-playback-start"), i18n("Play/Pause"), this);
mActionCollection->addAction("play_pause", mPlayPauseAction);
mPlayPauseAction->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_X));
connect(mPlayPauseAction, SIGNAL(triggered()), SLOT(togglePlayback()));
mNextAction = new KAction(KIcon("media-skip-forward"), i18n("Next Song"), this);
mActionCollection->addAction("next_song", mNextAction);
mNextAction->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_C));
connect(mNextAction, SIGNAL(triggered()), SLOT(jumpToNextSong()));
mPreviousAction = new KAction(KIcon("media-skip-backward"), i18n("Previous Song"), this);
mActionCollection->addAction("previous_song", mPreviousAction);
mPreviousAction->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_Z));
connect(mPreviousAction, SIGNAL(triggered()), SLOT(jumpToPreviousSong()));
KAction *lNextTabAction = new KAction(i18n("Activate Next Tab"), this);
lNextTabAction->setShortcut(KStandardShortcut::tabNext());
mActionCollection->addAction("next_tab", lNextTabAction);
addAction((QAction *)lNextTabAction);
connect(lNextTabAction, SIGNAL(triggered()), this, SLOT(activateNextTab()));
KAction *lPreviousTabAction = new KAction(i18n("Activate Previous Tab"), this);
lPreviousTabAction->setShortcut(KStandardShortcut::tabPrev());
mActionCollection->addAction("previous_tab", lPreviousTabAction);
addAction((QAction *)lPreviousTabAction);
connect(lPreviousTabAction, SIGNAL(triggered()), this, SLOT(activatePreviousTab()));
mActionCollection->readSettings();
}
void BBMainWindow::setupControls()
{
mPlaybackControls = new QWidget();
mHistoryMenu = new QMenu(this);
mPreviousButton = new QToolButton(mPlaybackControls);
mPreviousButton->setIconSize(QSize(32,32));
mPreviousButton->setDefaultAction(mPreviousAction);
mPreviousButton->setAutoRaise(true);
mPreviousButton->setPopupMode(QToolButton::MenuButtonPopup);
mPreviousButton->setMenu(mHistoryMenu);
mPreviousButton->setEnabled(false);
mPlayPauseButton = new QToolButton(mPlaybackControls);
mPlayPauseButton->setIconSize(QSize(32,32));
mPlayPauseButton->setAutoRaise(true);
mPlayPauseButton->setDefaultAction(mPlayPauseAction);
mPlayQueueMenu = new QMenu(this);
mNextButton = new QToolButton(mPlaybackControls);
mNextButton->setIconSize(QSize(32,32));
mNextButton->setDefaultAction(mNextAction);
mNextButton->setAutoRaise(true);
mNextButton->setPopupMode(QToolButton::MenuButtonPopup);
mNextButton->setMenu(0);
mPositionSlider = new Phonon::SeekSlider(mPlaybackControls);
mPositionSlider->setIconVisible(false);
mShuffleCheckBox = new QCheckBox(i18n("Shuffle"), mPlaybackControls);
connect(mShuffleCheckBox, SIGNAL(toggled(bool)), SLOT(updateShuffleStatus()));
mVolumeSlider = new Phonon::VolumeSlider(mPlaybackControls);
mVolumeSlider->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
mVolumeSlider->setMuteVisible(false);
mElapsedTimeLabel = new QLabel("00:00", mPlaybackControls);
mElapsedTimeLabel->setFont(KGlobalSettings::generalFont());
mElapsedTimeLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
mCurrentSongLabel = new BBTitleLabel(mPlaybackControls);
mCurrentSongLabel->setFont(KGlobalSettings::generalFont());
mCurrentSongLabel->setNewText(i18n("Hey ho! Let's go!"));
QHBoxLayout *lHLayout = new QHBoxLayout;
lHLayout->addWidget(mPreviousButton);
lHLayout->addWidget(mPlayPauseButton);
lHLayout->addWidget(mNextButton);
QVBoxLayout *lVLayout = new QVBoxLayout;
lHLayout->addLayout(lVLayout);
QHBoxLayout *lHLayout2 = new QHBoxLayout;
lVLayout->addLayout(lHLayout2);
lHLayout2->addWidget(mCurrentSongLabel);
lHLayout2->addWidget(mElapsedTimeLabel);
QHBoxLayout *lHLayout3 = new QHBoxLayout;
lVLayout->addLayout(lHLayout3);
lHLayout3->addWidget(mShuffleCheckBox);
lHLayout3->addWidget(mPositionSlider);
lHLayout3->addWidget(mVolumeSlider);
mPlaybackControls->setLayout(lHLayout);
}
void BBMainWindow::closeEvent(QCloseEvent *pEvent)
{
saveSession();
pEvent->accept();
kapp->quit();
}
void BBMainWindow::setActiveTab(int pCurrentIndex)
{
int lCount = count();
for(int i = 0; i < lCount; ++i)
{
QWidget *lMenuBar = tabBar()->tabButton(i, QTabBar::RightSide);
if(lMenuBar)
lMenuBar->setEnabled(i == pCurrentIndex);
}
if(mCollectionTab->tabNumber() == pCurrentIndex)
mCollectionTab->embedControls(mPlaybackControls);
else if(mFileSystemTab->tabNumber() == pCurrentIndex)
mFileSystemTab->embedControls(mPlaybackControls);
else if(mStreamsTab->tabNumber() == pCurrentIndex)
mStreamsTab->embedControls(mPlaybackControls);
}
void BBMainWindow::setCurrentPlaylistSystem(int pTabNumber)
{
if(mPlaylistSystem)
mPlaylistSystem->disconnect();
if(pTabNumber == mCollectionTab->tabNumber())
mPlaylistSystem = mCollectionTab;
if(pTabNumber == mFileSystemTab->tabNumber())
mPlaylistSystem = mFileSystemTab;
if(pTabNumber == mStreamsTab->tabNumber())
mPlaylistSystem = mStreamsTab;
connect(mPlaylistSystem, SIGNAL(songQueryReady(BBSongQueryJob)), this, SLOT(playNextSong(BBSongQueryJob)));
}
void BBMainWindow::showConfigDialog()
{
if(KConfigDialog::showDialog("settings"))
return;
KConfigDialog *dialog = new KConfigDialog(this, "settings", BBSettings::self());
QWidget *generalSettingsDlg = new QWidget;
Ui_DirectoriesPage lDirPage;
lDirPage.setupUi(generalSettingsDlg);
dialog->addPage(generalSettingsDlg, i18n("File locations"), "folder-sound");
//connect(dialog, SIGNAL(settingsChanged(QString)), m_view, SLOT(settingsChanged()));
dialog->show();
}
void BBMainWindow::showHelp()
{
KToolInvocation::invokeHelp();
}
void BBMainWindow::showAboutDialog()
{
KAboutApplicationDialog *lDialog = new KAboutApplicationDialog(mAboutData, this);
lDialog->show();
connect(lDialog, SIGNAL(finished()), lDialog, SLOT(deleteLater()));
}
void BBMainWindow::showShortcutsDialog()
{
KShortcutsDialog lDialog(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this);
lDialog.addCollection(mActionCollection, "BoomBox");
lDialog.addCollection(mCollectionTab->actionCollection(), i18n("Music Collection Tab"));
lDialog.addCollection(mFileSystemTab->actionCollection(), i18n("File System Tab"));
lDialog.configure();
}
void BBMainWindow::togglePlayback()
{
if(mMediaObject->currentSource().type() == Phonon::MediaSource::Empty)
jumpToNextSong(true);
else
{
Phonon::State lState = mMediaObject->state();
if(lState == Phonon::PlayingState || lState == Phonon::BufferingState)
mMediaObject->pause();
else
mMediaObject->play();
}
}
void BBMainWindow::queueNextSong()
{
QList<QAction *> lQueue = mPlayQueueMenu->actions();
if(!lQueue.isEmpty())
{
QAction *lNext = lQueue.takeFirst();
mPlayQueueMenu->removeAction(lNext);
BBSongRef *lSongRef = (BBSongRef *)lNext->data().value<void *>();
setCurrentIndex(lSongRef->mTabIndex);
setCurrentPlaylistSystem(lSongRef->mTabIndex);
BBSongQueryJob lJob;
lJob.mOnlyPlaceInQueue = true;
lJob.mResumePlaying = true;
lJob.mSong = lSongRef->mSong;
playNextSong(lJob);
delete lSongRef;
delete lNext;
if(lQueue.isEmpty())
mNextButton->setMenu(0);
}
else
{
BBSongQueryJob lJob;
lJob.mShuffle = mShuffleCheckBox->checkState();
lJob.mOnlyPlaceInQueue = true;
lJob.mResumePlaying = true;
mPlaylistSystem->queryNextSong(lJob);
}
}
void BBMainWindow::jumpToNextSong(bool pStartPlayback)
{
QList<QAction *> lQueue = mPlayQueueMenu->actions();
if(!lQueue.isEmpty())
{
QAction *lNext = lQueue.takeFirst();
mPlayQueueMenu->removeAction(lNext);
BBSongRef *lSongRef = (BBSongRef *)lNext->data().value<void *>();
setCurrentIndex(lSongRef->mTabIndex);
setCurrentPlaylistSystem(lSongRef->mTabIndex);
setCurrentSong(lSongRef->mSong, pStartPlayback);
delete lSongRef;
delete lNext;
if(lQueue.isEmpty())
mNextButton->setMenu(0);
}
else
{
BBSongQueryJob lJob;
lJob.mShuffle = mShuffleCheckBox->checkState();
lJob.mOnlyPlaceInQueue = false;
lJob.mResumePlaying = pStartPlayback || mMediaObject->state() == Phonon::PlayingState;;
mPlaylistSystem->queryNextSong(lJob);
}
}
void BBMainWindow::jumpToPreviousSong()
{
QList<QAction *> lHistory = mHistoryMenu->actions();
if(lHistory.isEmpty())
return;
QAction *lPrevious = lHistory.takeFirst();
mHistoryMenu->removeAction(lPrevious);
BBSongRef *lSongRef = (BBSongRef *)lPrevious->data().value<void *>();
setCurrentIndex(lSongRef->mTabIndex);
setCurrentPlaylistSystem(lSongRef->mTabIndex);
setCurrentSong(lSongRef->mSong, false, false);
delete lSongRef;
delete lPrevious;
if(lHistory.isEmpty())
mPreviousButton->setEnabled(false);
}
void BBMainWindow::playNextSong(const BBSongQueryJob &pJob)
{
Phonon::MediaSource lMediaSource = mMediaObject->currentSource();
if(lMediaSource.type() == Phonon::MediaSource::Url
|| lMediaSource.type() == Phonon::MediaSource::LocalFile)
{
addCurrentSongToHistory();
}
mNextSong = pJob.mSong;
KUrl lSongUrl = mPlaylistSystem->songUrl(pJob.mSong);
if(pJob.mOnlyPlaceInQueue && mMediaObject->state() == Phonon::PlayingState)
mMediaObject->enqueue(lSongUrl);
else
{
mMediaObject->stop();
mMediaObject->clearQueue();
mMediaObject->setCurrentSource(lSongUrl);
if(pJob.mResumePlaying)
mMediaObject->play();
}
}
void BBMainWindow::setCurrentSong(QVariant pSong, bool pStartPlayback, bool pAddToHistory)
{
KUrl lNewSongUrl = mPlaylistSystem->songUrl(pSong);
if(lNewSongUrl.isEmpty())
return;
Phonon::MediaSource lMediaSource = mMediaObject->currentSource();
if(pAddToHistory && (lMediaSource.type() == Phonon::MediaSource::Url
|| lMediaSource.type() == Phonon::MediaSource::LocalFile))
{
addCurrentSongToHistory();
}
bool lResumePlaying = mMediaObject->state() == Phonon::PlayingState;
mNextSong = pSong;
mMediaObject->stop();
mMediaObject->clearQueue();
mMediaObject->setCurrentSource(lNewSongUrl);
if(pStartPlayback || lResumePlaying)
mMediaObject->play();
}
void BBMainWindow::addToPlayQueue(const QVariant &pSong, const QString &pDisplayText, int pTabNumber)
{
QAction *lQueueAction = new QAction(this);
BBSongRef *lSongRef = new BBSongRef();
lSongRef->mSong = pSong;
lSongRef->mTabIndex = pTabNumber;
lQueueAction->setData(QVariant::fromValue((void *)lSongRef));
lQueueAction->setText(pDisplayText);
connect(lQueueAction, SIGNAL(triggered()), this, SLOT(playSongFromQueue()));
mPlayQueueMenu->addAction(lQueueAction);
mNextButton->setMenu(mPlayQueueMenu);
}
void BBMainWindow::addCurrentSongToHistory()
{
QAction *lHistoryAction = new QAction(this);
BBSongRef *lSongRef = new BBSongRef();
lSongRef->mSong = mPlayingPlaylistSystem->currentSong();
lSongRef->mTabIndex = mPlayingPlaylistSystem->tabNumber();
lHistoryAction->setData(QVariant::fromValue((void *)lSongRef));
lHistoryAction->setText(mCurrentSongLabel->text());
connect(lHistoryAction, SIGNAL(triggered()), this, SLOT(playSongFromHistory()));
QList<QAction *> lHistory = mHistoryMenu->actions();
mHistoryMenu->insertAction(lHistory.isEmpty() ? 0 : lHistory.first(), lHistoryAction);
if(lHistory.count() == gMaxHistory)
{
mHistoryMenu->removeAction(lHistory.last());
delete lHistory.last();
}
mPreviousButton->setEnabled(true);
}
void BBMainWindow::updateElapsedTime(qint64 pElapsedTime)
{
QTime lTime(0, pElapsedTime/60000, (pElapsedTime/1000) % 60);
mElapsedTimeLabel->setText(lTime.toString("mm:ss"));
if(qAbs(pElapsedTime - mLastUpdatedTime) > 1500) {
mMpris2Player->notifySeeked(pElapsedTime * 1000);
}
mLastUpdatedTime = pElapsedTime;
}
void BBMainWindow::updateCurrentSong(const Phonon::MediaSource &pNewSource)
{
Q_UNUSED(pNewSource)
mPlaylistSystem->setCurrentSong(mNextSong);
mCurrentSongLabel->setNewText(mPlaylistSystem->displayString(mNextSong));
//at this time we can be sure the origin for current song is the current playlist system
mPlayingPlaylistSystem = mPlaylistSystem;
}
void BBMainWindow::updatePhononState(Phonon::State pNewState, Phonon::State pOldState)
{
switch (pNewState)
{
case Phonon::ErrorState:
if(mMediaObject->errorType() == Phonon::FatalError)
QMessageBox::warning(this, i18n("Fatal Error"), mMediaObject->errorString());
else
QMessageBox::warning(this, i18n("Error"), mMediaObject->errorString());
break;
case Phonon::PlayingState:
mPlayPauseAction->setIcon(KIcon("media-playback-pause"));
break;
case Phonon::StoppedState:
mPlayPauseAction->setIcon(KIcon("media-playback-start"));
mElapsedTimeLabel->setText("00:00");
break;
case Phonon::PausedState:
mPlayPauseAction->setIcon(KIcon("media-playback-start"));
break;
case Phonon::BufferingState:
mBufferingStatusTimer->start(200);
break;
default:
break;
}
if(pOldState == Phonon::BufferingState)
{
if(mPlaylistSystem == mStreamsTab)
updateMetaDataDisplay();
else
mCurrentSongLabel->setNewText(mPlaylistSystem->displayString(mPlaylistSystem->currentSong()));
mBufferingStatusTimer->stop();
}
if(mMpris2Player) {
mMpris2Player->notifyChangedProperty(QLatin1String("PlaybackStatus"));
}
}
void BBMainWindow::playSongFromHistory()
{
QAction *lSenderAction = qobject_cast<QAction *>(sender());
if(lSenderAction)
{
BBSongRef *lSongRef = (BBSongRef *)lSenderAction->data().value<void *>();
setCurrentIndex(lSongRef->mTabIndex);
setCurrentPlaylistSystem(lSongRef->mTabIndex);
setCurrentSong(lSongRef->mSong, false, false);
delete lSongRef;
QList<QAction *> lHistory = mHistoryMenu->actions();
QAction *lAction;
while((lAction = lHistory.takeFirst()) != lSenderAction)
{
mHistoryMenu->removeAction(lAction);
lAction->deleteLater();
}
lSenderAction->deleteLater();
if(lHistory.isEmpty())
mPreviousButton->setEnabled(false);
}
}
void BBMainWindow::playSongFromQueue()
{
QAction *lSenderAction = qobject_cast<QAction *>(sender());
if(lSenderAction)
{
BBSongRef *lSongRef = (BBSongRef *)lSenderAction->data().value<void *>();
setCurrentIndex(lSongRef->mTabIndex);
setCurrentPlaylistSystem(lSongRef->mTabIndex);
setCurrentSong(lSongRef->mSong, false);
delete lSongRef;
QList<QAction *> lQueue = mPlayQueueMenu->actions();
QAction *lAction;
while((lAction = lQueue.takeFirst()) != lSenderAction)
{
mPlayQueueMenu->removeAction(lAction);
lAction->deleteLater();
}
lSenderAction->deleteLater();
if(lQueue.isEmpty())
mNextButton->setMenu(0);
}
}
void BBMainWindow::changeEvent(QEvent *pEvent)
{
if(pEvent->type() == QEvent::FontChange)
{
mCurrentSongLabel->setFont(KGlobalSettings::generalFont());
}
KTabWidget::changeEvent(pEvent);
}
void BBMainWindow::saveSession()
{
KSharedConfigPtr lConfig = KGlobal::config();
KConfigGroup lSessionGroup(lConfig, "session");
lSessionGroup.writeEntry("shuffle_enabled", mShuffleCheckBox->isChecked());
lSessionGroup.writeEntry("active_tab", this->currentIndex());
mCollectionTab->saveSession(lSessionGroup);
mFileSystemTab->saveSession(lSessionGroup);
mStreamsTab->saveSession(lSessionGroup);
lConfig->sync();
}
void BBMainWindow::readSession()
{
KSharedConfigPtr lConfig = KGlobal::config();
KConfigGroup lSessionGroup(lConfig, "session");
mCollectionTab->readSession(lSessionGroup);
mFileSystemTab->readSession(lSessionGroup);
mStreamsTab->readSession(lSessionGroup);
mShuffleCheckBox->setChecked(lSessionGroup.readEntry("shuffle_enabled", false));
int lActiveTab = lSessionGroup.readEntry("active_tab", 0);
setCurrentIndex(lActiveTab);
setCurrentPlaylistSystem(lActiveTab);
mPlayingPlaylistSystem = mPlaylistSystem;
// only create mpris interface after everything else is constructed.
// doing otherwise causes dbus deadlock with the "now playing" mpris
// daemon from ktp, running in kded
QDBusConnection lConnection = QDBusConnection::sessionBus();
lConnection.registerService("org.mpris.MediaPlayer2.BoomBox");
mMpris2Player = new Mpris2Player(this);
new MediaPlayer2Adaptor(mMpris2Player);
new PlayerAdaptor(mMpris2Player);
lConnection.registerObject("/org/mpris/MediaPlayer2", mMpris2Player);
}
void BBMainWindow::activateNextTab()
{
setCurrentIndex((currentIndex() + 1) % count());
}
void BBMainWindow::activatePreviousTab()
{
setCurrentIndex((currentIndex() + count() - 1) % count());
}
void BBMainWindow::updateMetaDataDisplay()
{
// Streams can have changing metadata
if(!mMediaObject->isSeekable()) {
QMultiMap<QString, QString> lMetaData = mMediaObject->metaData();
QString lArtist = lMetaData.value("ARTIST");
QString lTitle = lMetaData.value("TITLE");
QString lNewName;
if(!lArtist.isEmpty())
{
if(!lTitle.isEmpty())
lNewName = QString::fromAscii("%1 - %2").arg(lArtist, lTitle);
else
lNewName = lArtist;
}
else
{
if(!lTitle.isEmpty())
lNewName = lTitle;
else
lNewName = mPlaylistSystem->displayString(mPlaylistSystem->currentSong());
}
mCurrentSongLabel->setNewText(lNewName);
}
// always update mpris clients
if(mMpris2Player) {
mMpris2Player->notifyChangedProperty(QLatin1String("Metadata"));
}
}
void BBMainWindow::updateShuffleStatus() {
if(mMpris2Player) {
mMpris2Player->notifyChangedProperty(QLatin1String("Shuffle"));
}
}
void BBMainWindow::setBufferingStatus()
{
mCurrentSongLabel->setNewText(i18n("Buffering..."));
}

188
boombox/src/bbmainwindow.h Normal file
View file

@ -0,0 +1,188 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef BBMAINWINDOW_H
#define BBMAINWINDOW_H
#include <KAboutData>
#include <KTabWidget>
#include <KMenuBar>
#include <QCheckBox>
#include <QEvent>
#include <QLabel>
#include <QModelIndex>
#include <QVariant>
#include <phonon/phononnamespace.h>
namespace Phonon
{
class SeekSlider;
class VolumeSlider;
class AudioOutput;
class MediaObject;
class MediaSource;
}
class BBCollectionTab;
class BBFileSystemTab;
class BBStreamsTab;
class BBPlaylistSystem;
class Mpris2Player;
class KActionCollection;
class KAction;
class QToolButton;
class BBTitleLabel : public QLabel
{
Q_OBJECT
public:
explicit BBTitleLabel(QWidget *pParent = 0);
virtual QSize minimumSizeHint() const {return QSize(1, 1);}
public slots:
void advanceAnimation();
void setNewText(const QString &pText);
protected:
virtual void paintEvent(QPaintEvent *);
virtual void resizeEvent(QResizeEvent *pEvent);
float mX, mVx;
QTimer *mTimer;
QPixmap mSecondPic;
const int mMargin, mUpdateInterval;
const float mSpeed;
};
class BBTabMenuBar : public KMenuBar
{
public:
BBTabMenuBar(QWidget *pParent = 0) : KMenuBar(pParent) {}
virtual bool event(QEvent *pEvent)
{
if(!isEnabled() && pEvent->type() == QEvent::MouseButtonPress)
{
pEvent->ignore();
return false;
}
else
return KMenuBar::event(pEvent);
}
};
struct BBSongQueryJob
{
//the query
bool mShuffle;
//the result
QVariant mSong;
//from mainwindow, to be returned again along with result
bool mOnlyPlaceInQueue;
bool mResumePlaying;
};
struct BBSongRef
{
QVariant mSong;
int mTabIndex;
};
class BBMainWindow : public KTabWidget
{
Q_OBJECT
public:
BBMainWindow(const KAboutData *pAboutData, QWidget *parent = 0);
~BBMainWindow();
bool shuffleActive() { return mShuffleCheckBox->isChecked(); }
void setShuffle(bool pEnable) { mShuffleCheckBox->setChecked(pEnable); }
void saveSession();
void readSession();
void setCurrentSong(QVariant pSong, bool pStartPlayback = true, bool pAddToHistory = true);
void addToPlayQueue(const QVariant &pSong, const QString &pDisplayText, int pTabNumber);
void addCurrentSongToHistory();
void setCurrentPlaylistSystem(int pTabNumber);
public slots:
void setActiveTab(int pCurrentIndex);
void showConfigDialog();
void showHelp();
void showAboutDialog();
void showShortcutsDialog();
void updatePhononState(Phonon::State pNewState, Phonon::State pOldState);
void togglePlayback();
void jumpToNextSong(bool pStartPlayback = false);
void jumpToPreviousSong();
void updateElapsedTime(qint64 pElapsedTime);
void updateCurrentSong(const Phonon::MediaSource &pNewSource);
void queueNextSong();
void playNextSong(const BBSongQueryJob &pJob);
void activateNextTab();
void activatePreviousTab();
virtual void changeEvent(QEvent *);
void updateMetaDataDisplay();
void updateShuffleStatus();
void setBufferingStatus();
void playSongFromHistory();
void playSongFromQueue();
protected:
void closeEvent(QCloseEvent *pEvent);
void setupActions();
void setupControls();
BBCollectionTab *mCollectionTab;
BBFileSystemTab *mFileSystemTab;
BBStreamsTab *mStreamsTab;
KActionCollection *mActionCollection;
Phonon::SeekSlider *mPositionSlider;
Phonon::MediaObject *mMediaObject;
Phonon::AudioOutput *mAudioOutput;
Phonon::VolumeSlider *mVolumeSlider;
QVariant mNextSong;
KAction *mPlayPauseAction;
KAction *mNextAction;
KAction *mPreviousAction;
const KAboutData *mAboutData;
QLabel *mElapsedTimeLabel;
BBTitleLabel *mCurrentSongLabel;
QCheckBox *mShuffleCheckBox;
QToolButton *mPlayPauseButton;
QToolButton *mNextButton;
QToolButton *mPreviousButton;
QWidget *mPlaybackControls;
QMenu *mHistoryMenu;
QMenu *mPlayQueueMenu;
BBPlaylistSystem *mPlaylistSystem;
BBPlaylistSystem *mPlayingPlaylistSystem;
QTimer *mBufferingStatusTimer;
qint64 mLastUpdatedTime;
Mpris2Player *mMpris2Player;
friend class Mpris2Player;
};
extern BBMainWindow *gMainWindow;
#endif // BBMAINWINDOW_H

141
boombox/src/bbmetadata.cpp Normal file
View file

@ -0,0 +1,141 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "bbmetadata.h"
#include <taglib/fileref.h>
#include <taglib/tag.h>
#include <QFileInfo>
#include <QTime>
BBMetaData::BBMetaData(const BBMetaData &pMetaData)
{
mPath = pMetaData.mPath;
mFileID = pMetaData.mFileID;
mTitle = pMetaData.mTitle;
mArtist = pMetaData.mArtist;
mAlbum = pMetaData.mAlbum;
mLength = pMetaData.mLength;
mYear = pMetaData.mYear;
mGenre = pMetaData.mGenre;
mTrack = pMetaData.mTrack;
mComment = pMetaData.mComment;
}
void BBMetaData::GetInfoFrom(const QString &pPath, const QString &pFileName)
{
mPath = pPath;
mFileName = pFileName;
QString lTemp = pFileName.toLower();
if(lTemp.endsWith(".ogg") || lTemp.endsWith(".mp3") || lTemp.endsWith(".mpc") || lTemp.endsWith(".flac"))
{
TagLib::FileRef lFile(QFile::encodeName(QString("%1/%2").arg(pPath, pFileName)));
TagLib::Tag *lTag = lFile.tag();
TagLib::AudioProperties *lAudio = lFile.audioProperties();
if(lTag != NULL)
{
mTitle = QString::fromUtf8(lTag->title().toCString(true)).simplified();
mArtist = QString::fromUtf8(lTag->artist().toCString(true)).simplified();
mAlbum = QString::fromUtf8(lTag->album().toCString(true)).simplified();
mGenre = QString::fromUtf8(lTag->genre().toCString(true)).simplified();
mComment = QString::fromUtf8(lTag->comment().toCString(true)).simplified();
mYear = QString::number(lTag->year());
mTrack = QString::number(lTag->track());
}
if(lAudio != NULL)
{
QTime lTime(0, lAudio->length()/60, lAudio->length()%60);
mLength = lTime.toString("mm:ss");
}
}
CheckInfo();
}
void BBMetaData::CheckInfo()
{
QString lReplacer("#-UNKNOWN-#");
if(mTitle.isEmpty())
{
QFileInfo fi(mPath);
mTitle = fi.baseName();
mTitle.replace('_', " ");
}
if(mArtist.isEmpty())
mArtist = lReplacer;
if(mAlbum.isEmpty())
mAlbum = lReplacer;
if(mGenre.isEmpty())
mGenre = lReplacer;
if(mYear == "0")
mYear = lReplacer;
if(mTrack == "0")
mTrack = lReplacer;
}
void BBMetaData::ChangeFile(const QString &pPath, const QString &pCategory, const QString &pNewValue)
{
QString lTemp = pPath.toLower();
if(!(lTemp.endsWith(".ogg") || lTemp.endsWith(".mp3") || lTemp.endsWith(".mpc") || lTemp.endsWith(".flac")))
return;
TagLib::FileRef lFile(QFile::encodeName(pPath));
TagLib::Tag *lTag = lFile.tag();
if(lTag != NULL)
{
TagLib::String lNewValue(pNewValue.toUtf8().data(), TagLib::String::UTF8);
if(pCategory == "title")
lTag->setTitle(lNewValue);
else if(pCategory == "artist")
lTag->setArtist(lNewValue);
else if(pCategory == "album")
lTag->setAlbum(lNewValue);
else if(pCategory == "comment")
lTag->setComment(lNewValue);
else if(pCategory == "genre")
lTag->setGenre(lNewValue);
else if(pCategory == "year")
lTag->setYear(pNewValue.toUInt());
else if(pCategory == "track")
lTag->setTrack(pNewValue.toUInt());
lFile.save();
}
}
void BBMetaData::WriteInfoToFile()
{
TagLib::FileRef lFile(QFile::encodeName(QString("%1/%2").arg(mPath, mFileName)));
TagLib::Tag *lTag = lFile.tag();
if(lTag != NULL)
{
lTag->setTitle(TagLib::String(mTitle.toUtf8().data(), TagLib::String::UTF8));
lTag->setArtist(TagLib::String(mArtist.toUtf8().data(), TagLib::String::UTF8));
lTag->setAlbum(TagLib::String(mAlbum.toUtf8().data(), TagLib::String::UTF8));
lTag->setComment(TagLib::String(mComment.toUtf8().data(), TagLib::String::UTF8));
lTag->setGenre(TagLib::String(mGenre.toUtf8().data(), TagLib::String::UTF8));
lTag->setYear(mYear.toUInt());
lTag->setTrack(mTrack.toUInt());
lFile.save();
}
}

40
boombox/src/bbmetadata.h Normal file
View file

@ -0,0 +1,40 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef BBMETADATA_H
#define BBMETADATA_H
#include <QString>
class BBMetaData
{
public:
BBMetaData() {}
BBMetaData(const BBMetaData &pMetaData);
void GetInfoFrom(const QString &pPath, const QString& pFileName);
void WriteInfoToFile();
static void ChangeFile(const QString &pPath, const QString &pCategory, const QString &pNewValue);
void CheckInfo();
QString mTitle, mArtist, mAlbum, mLength, mYear, mGenre, mTrack, mComment, mPath, mFileName;
int mFileID;
};
#endif

View file

@ -0,0 +1,438 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "bbmetainfodialog.h"
#include "kmessagebox.h"
#include "ui_bbmetainfodialog.h"
#include "bbmetadata.h"
#include "bbsetmodel.h"
#include "bbsettings.h"
#include "bbcollectiontab.h"
#include <sqlite3.h>
BBMetaInfoDialog::BBMetaInfoDialog(QWidget *parent, BBCollectionTab *pCollectionTab):
QDialog(parent), mUI(new Ui::BBMetaInfoDialog), mCollectionTab(pCollectionTab)
{
mUI->setupUi(this);
connect(this, SIGNAL(accepted()), this, SLOT(saveValues()));
connect(mUI->mGuessButton, SIGNAL(clicked()), this, SLOT(guessFieldNames()));
}
BBMetaInfoDialog::~BBMetaInfoDialog()
{
delete mUI;
}
void BBMetaInfoDialog::changeEvent(QEvent *e)
{
QDialog::changeEvent(e);
switch (e->type()) {
case QEvent::LanguageChange:
mUI->retranslateUi(this);
break;
default:
break;
}
}
bool BBMetaInfoDialog::fillInValues(const QModelIndex &pModelIndex)
{
mIndex = pModelIndex;
mFileInfo = QFileInfo(QString("%1/%2").arg(pModelIndex.data(BBPathRole).toString(),
pModelIndex.data(BBFileNameRole).toString()));
if(!mFileInfo.exists())
{
KMessageBox::sorry(this, i18n("Sorry, the file does not exist anymore. You should run an update to keep the BoomBox database in sync with the filesystem."), mFileInfo.canonicalFilePath());
return false;
}
if(!mFileInfo.isWritable())
{
KMessageBox::sorry(this, i18n("Sorry, the file is not writable. Make sure you have correct permissions to alter the file and then try again."), mFileInfo.canonicalFilePath());
return false;
}
mMetaData.GetInfoFrom(mFileInfo.absolutePath(), mFileInfo.fileName());
mUI->mPathDisplay->setText(QString("%1/%2").arg(mMetaData.mPath, mMetaData.mFileName));
mUI->mArtistEdit->setText(mMetaData.mArtist);
mUI->mAlbumEdit->setText(mMetaData.mAlbum);
mUI->mTitleEdit->setText(mMetaData.mTitle);
mUI->mTrackSpinner->setValue(mMetaData.mTrack.toInt());
mUI->mYearSpinner->setValue(mMetaData.mYear.toInt());
mUI->mGenreEdit->setText(mMetaData.mGenre);
mUI->mCommentEdit->setPlainText(mMetaData.mComment);
return true;
}
void BBMetaInfoDialog::saveValues()
{
int lFileID = mIndex.data(BBFileIDRole).toInt();
bool lValuesChanged = false;
mDatabase.connect(BBSettings::fileNameDB());
if(mUI->mArtistEdit->text() != mMetaData.mArtist)
{
lValuesChanged = true;
mMetaData.mArtist = mUI->mArtistEdit->text();
mDatabase.executeStatement(
QString("UPDATE songs SET artist = '%1' WHERE file_ID = '%2'").arg(BBDatabase::prepareString(mMetaData.mArtist)).arg(lFileID)
);
}
if(mUI->mTitleEdit->text() != mMetaData.mTitle)
{
lValuesChanged = true;
mMetaData.mTitle = mUI->mTitleEdit->text();
mDatabase.executeStatement(
QString("UPDATE songs SET title = '%1' WHERE file_ID = '%2'").arg(BBDatabase::prepareString(mMetaData.mTitle)).arg(lFileID)
);
}
if(mUI->mGenreEdit->text() != mMetaData.mGenre)
{
lValuesChanged = true;
mMetaData.mGenre = mUI->mGenreEdit->text();
mDatabase.executeStatement(
QString("UPDATE songs SET genre = '%1' WHERE file_ID = '%2'").arg(BBDatabase::prepareString(mMetaData.mGenre)).arg(lFileID)
);
}
QString lComment = mUI->mCommentEdit->document()->toPlainText();
if(lComment != mMetaData.mComment)
{
lValuesChanged = true;
mMetaData.mComment = lComment;
mDatabase.executeStatement(
QString("UPDATE songs SET comment = '%1' WHERE file_ID = '%2'").arg(BBDatabase::prepareString(mMetaData.mComment)).arg(lFileID)
);
}
QString lTrack = QString::number(mUI->mTrackSpinner->value());
if(lTrack != mMetaData.mTrack)
{
lValuesChanged = true;
mMetaData.mTrack = lTrack;
mDatabase.executeStatement(
QString("UPDATE songs SET track = '%1' WHERE file_ID = '%2'").arg(BBDatabase::prepareString(mMetaData.mTrack)).arg(lFileID)
);
}
QString lYear = QString::number(mUI->mYearSpinner->value());
if(lYear != mMetaData.mYear)
{
lValuesChanged = true;
mMetaData.mYear = lYear;
mDatabase.executeStatement(
QString("UPDATE songs SET year = '%1' WHERE file_ID = '%2'").arg(BBDatabase::prepareString(mMetaData.mYear)).arg(lFileID)
);
}
if(mUI->mAlbumEdit->text() != mMetaData.mAlbum)
{
lValuesChanged = true;
mMetaData.mAlbum = mUI->mAlbumEdit->text();
int lAlbumID;
BBResultTable lResult;
mDatabase.getTable(QString(
"SELECT DISTINCT album_ID FROM songs JOIN albums USING (album_ID) "
"WHERE album = '%1' AND path = '%2'").arg(BBDatabase::prepareString(mMetaData.mAlbum),
BBDatabase::prepareString(mMetaData.mPath)),
lResult);
if(lResult.mNumRows == 0)
{
mDatabase.executeStatement(QString(
"INSERT INTO albums (path, album, is_VA, cover_art_path) "
"VALUES ('%1','%2','%3','%4')").arg(BBDatabase::prepareString(mMetaData.mPath),
BBDatabase::prepareString(mMetaData.mAlbum),
QString::number(0), QString())
);
lAlbumID = sqlite3_last_insert_rowid(mDatabase.getDatabase());
}
else
{
lAlbumID = QString::fromUtf8(lResult.at(1, 0)).toInt();
}
mDatabase.freeTable(lResult);
mDatabase.executeStatement(
QString("UPDATE songs SET album_ID = %1 WHERE file_ID = %2").arg(lAlbumID).arg(lFileID)
);
}
if(lValuesChanged)
{
mMetaData.WriteInfoToFile();
mCollectionTab->refreshAllViewsAndUpdateItem(mIndex);
}
}
QStringList BBMetaInfoDialog::splitName(QString lName)
{
QStringList lSplitFileName = lName.split(QRegExp("[\\.-]")); //split on dot or hyphen
QStringList lFields;
foreach (QString lField, lSplitFileName)
{
QString lSimp = lField.replace('_', ' ').simplified();
QRegExp lRegExp("^\\d\\d\\s");
if(lSimp.contains(lRegExp))
{
lFields << lSimp.left(2);
lSimp = lSimp.right(lSimp.length() - 2).simplified();
}
lRegExp = QRegExp("\\b(\\w+)\\b");
QStringList lDontTouch;
lDontTouch <<"a" <<"an" <<"the" <<"it" <<"this" <<"that" <<"but" <<"and" <<"or" <<"for" <<"so" <<"yet"
<<"in" <<"out" <<"on" <<"over" <<"of" <<"off" <<"to" <<"from" <<"by" <<"with";
int lPos = 0;
while((lPos = lRegExp.indexIn(lSimp, lPos)) != -1)
{
QString lMatch = lRegExp.cap(1).toLower();
if(!lDontTouch.contains(lMatch) || lPos == 0)
{
lMatch.replace(0, 1, lMatch.at(0).toUpper());
}
lSimp.replace(lPos, lMatch.length(), lMatch);
lPos += lRegExp.matchedLength();
}
lRegExp = QRegExp("\\s\\d\\d$");
if(lSimp.contains(lRegExp))
{
lFields << lSimp.left(lSimp.length() - 2).simplified();
lSimp = lSimp.right(2);
}
lFields << lSimp;
}
if(lFields.last().contains("mp3", Qt::CaseInsensitive) || lFields.last().contains("ogg", Qt::CaseInsensitive)
|| lFields.last().contains("flac", Qt::CaseInsensitive))
{
lFields.takeLast();
}
return lFields;
}
void BBMetaInfoDialog::guessFieldNames()
{
int lYear = -1, lTrack = -1;
QString lArtist, lAlbum, lTitle;
QStringList lFileFields = splitName(mFileInfo.fileName());
QString lFolderName = mFileInfo.canonicalPath();
int lLastSlash = lFolderName.lastIndexOf('/');
if(lLastSlash != -1)
lFolderName.remove(0, lLastSlash + 1);
QStringList lFolderFields = splitName(lFolderName);
bool lIsNumber;
int lFileNumberBitField = 0;
int lFolderNumberBitField = 0;
for(int i = 0; i < lFileFields.count(); ++i)
{
lFileFields.at(i).toInt(&lIsNumber);
if(lIsNumber)
lFileNumberBitField |= 1 << i;
}
for(int i = 0; i < lFolderFields.count(); ++i)
{
lFolderFields.at(i).toInt(&lIsNumber);
if(lIsNumber)
lFolderNumberBitField |= 1 << i;
}
switch(lFileFields.count())
{
case 1:
if(lFileNumberBitField != 0)
lTrack = lFileFields.at(0).toInt();
else
lTitle = lFileFields.at(0);
break;
case 2:
switch(lFileNumberBitField)
{
case 0:
lArtist = lFileFields.at(0);
lTitle = lFileFields.at(1);
break;
case 1:
case 3:
lTrack = lFileFields.at(0).toInt();
lTitle = lFileFields.at(1);
break;
case 2:
lAlbum = lFileFields.at(0);
lTrack = lFileFields.at(1).toInt();
break;
}
break;
case 3:
switch(lFileNumberBitField & 3) //only expect number in first two fields, otherwise maybe numeric title or such.
{
case 0:
lArtist = lFileFields.at(0);
lAlbum = lFileFields.at(1);
lTitle = lFileFields.at(2);
break;
case 1:
case 3:
lTrack = lFileFields.at(0).toInt();
lTitle = lFileFields.at(1);
break;
case 2:
lArtist = lFileFields.at(0);
lTrack = lFileFields.at(1).toInt();
lTitle = lFileFields.at(2);
break;
}
break;
case 4:
case 5:
case 6:
case 7:
switch(lFileNumberBitField & 7) //only expect number in first three fields, otherwise maybe numeric title or such.
{
case 0:
lArtist = lFileFields.at(0);
lAlbum = lFileFields.at(1);
lTitle = lFileFields.at(2);
break;
case 1:
case 3:
case 5:
case 7:
lTrack = lFileFields.at(0).toInt();
lArtist = lFileFields.at(1);
lTitle = lFileFields.at(2);
break;
case 2:
case 6:
lArtist = lFileFields.at(0);
lYear = lFileFields.at(1).toInt();
lAlbum = lFileFields.at(2);
lTitle = lFileFields.at(3);
break;
case 4:
lArtist = lFileFields.at(0);
lAlbum = lFileFields.at(1);
lTrack = lFileFields.at(2).toInt();
lTitle = lFileFields.at(3);
break;
}
break;
}
switch(lFolderFields.count())
{
case 1:
if(lFolderNumberBitField == 0)
lAlbum = lFolderFields.at(0);
break;
case 2:
switch(lFolderNumberBitField)
{
case 0:
if(lArtist.isEmpty())
lArtist = lFolderFields.at(0);
if(lAlbum.isEmpty())
lAlbum = lFolderFields.at(1);
break;
case 1:
if(lYear == -1)
lYear = lFolderFields.at(0).toInt();
if(lAlbum.isEmpty())
lAlbum = lFolderFields.at(1);
break;
case 2:
case 3:
if(lAlbum.isEmpty())
lAlbum = lFolderFields.at(0);
if(lYear == -1)
lYear = lFolderFields.at(1).toInt();
break;
}
break;
case 3:
case 4:
case 5:
case 6:
switch(lFolderNumberBitField & 15)
{
case 0:
if(lArtist.isEmpty())
lArtist = lFolderFields.at(0);
if(lAlbum.isEmpty())
lAlbum = lFolderFields.at(1) + " - " + lFolderFields.at(2);
break;
case 1:
if(lYear == -1)
lYear = lFolderFields.at(0).toInt();
if(lArtist.isEmpty())
lArtist = lFolderFields.at(1);
if(lAlbum.isEmpty())
lAlbum = lFolderFields.at(2);
break;
case 2:
case 3:
if(lArtist.isEmpty())
lArtist = lFolderFields.at(0);
if(lYear == -1)
lYear = lFolderFields.at(1).toInt();
if(lAlbum.isEmpty())
lAlbum = lFolderFields.at(2);
break;
case 4:
case 5:
case 6:
case 7:
if(lArtist.isEmpty())
lArtist = lFolderFields.at(0);
if(lAlbum.isEmpty())
lAlbum = lFolderFields.at(1);
if(lYear == -1)
lYear = lFolderFields.at(2).toInt();
break;
case 8:
if(lArtist.isEmpty())
lArtist = lFolderFields.at(0);
if(lAlbum.isEmpty())
lAlbum = lFolderFields.at(1) + " - " + lFolderFields.at(2);
if(lYear == -1)
lYear = lFolderFields.at(3).toInt();
break;
}
break;
}
int lCDNumber = 0;
if(lYear != -1)
mUI->mYearSpinner->setValue(lYear);
if(lTrack != -1)
{
lCDNumber = lTrack / 100;
lTrack = lTrack - lCDNumber*100;
mUI->mTrackSpinner->setValue(lTrack);
if(lCDNumber > 0 && !lAlbum.isEmpty())
lAlbum.append(QString::fromAscii(" CD %1").arg(lCDNumber));
}
if(!lArtist.isNull())
mUI->mArtistEdit->setText(lArtist);
if(!lAlbum.isNull())
mUI->mAlbumEdit->setText(lAlbum);
if(!lTitle.isNull())
mUI->mTitleEdit->setText(lTitle);
//TODO: if some fields are not filled out now, check folder name too.
}

View file

@ -0,0 +1,62 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef BBMETAINFODIALOG_H
#define BBMETAINFODIALOG_H
#include "bbmetadata.h"
#include "bbdatabase.h"
#include <QDialog>
#include <QFileInfo>
#include <QModelIndex>
class BBCollectionTab;
namespace Ui {
class BBMetaInfoDialog;
}
class BBMetaInfoDialog : public QDialog
{
Q_OBJECT
Q_DISABLE_COPY(BBMetaInfoDialog)
public:
BBMetaInfoDialog(QWidget *parent, BBCollectionTab *pCollectionTab);
virtual ~BBMetaInfoDialog();
bool fillInValues(const QModelIndex &pModelIndex);
protected:
virtual void changeEvent(QEvent *e);
protected slots:
void saveValues();
void guessFieldNames();
private:
QStringList splitName(QString lName);
Ui::BBMetaInfoDialog *mUI;
BBMetaData mMetaData;
BBDatabase mDatabase;
QModelIndex mIndex;
QFileInfo mFileInfo;
BBCollectionTab *mCollectionTab;
};
#endif // BBMETAINFODIALOG_H

View file

@ -0,0 +1,261 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<author>Simon Persson</author>
<class>BBMetaInfoDialog</class>
<widget class="QDialog" name="BBMetaInfoDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>284</width>
<height>324</height>
</rect>
</property>
<property name="windowTitle">
<string>Edit Song Tags</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="mPathLabel">
<property name="text">
<string>Path:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="mPathDisplay">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="mGuessButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Guess Values From File Name</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="mArtistLabel">
<property name="text">
<string>Artist:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>mArtistEdit</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="KLineEdit" name="mArtistEdit">
<property name="showClearButton" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="mAlbumLabel">
<property name="text">
<string>Album:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>mAlbumEdit</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="KLineEdit" name="mAlbumEdit">
<property name="showClearButton" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="mTitleLabel">
<property name="text">
<string>Title:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>mTitleEdit</cstring>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="KLineEdit" name="mTitleEdit">
<property name="showClearButton" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="mTrackLabel">
<property name="text">
<string>Track:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="KIntSpinBox" name="mTrackSpinner"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="mYearLabel">
<property name="text">
<string>Year:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>mYearSpinner</cstring>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="KIntSpinBox" name="mYearSpinner">
<property name="maximum">
<number>10000</number>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="mGenreLabel">
<property name="text">
<string>Genre:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>mGenreEdit</cstring>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="KLineEdit" name="mGenreEdit">
<property name="showClearButton" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="mCommentLabel">
<property name="text">
<string>Comment:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QPlainTextEdit" name="mCommentEdit">
<property name="tabChangesFocus">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="mButtonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KIntSpinBox</class>
<extends>QSpinBox</extends>
<header>knuminput.h</header>
</customwidget>
<customwidget>
<class>KLineEdit</class>
<extends>QLineEdit</extends>
<header>klineedit.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>mGuessButton</tabstop>
<tabstop>mArtistEdit</tabstop>
<tabstop>mAlbumEdit</tabstop>
<tabstop>mTitleEdit</tabstop>
<tabstop>mTrackSpinner</tabstop>
<tabstop>mYearSpinner</tabstop>
<tabstop>mGenreEdit</tabstop>
<tabstop>mCommentEdit</tabstop>
<tabstop>mButtonBox</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>mButtonBox</sender>
<signal>rejected()</signal>
<receiver>BBMetaInfoDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>320</x>
<y>295</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>mButtonBox</sender>
<signal>accepted()</signal>
<receiver>BBMetaInfoDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>252</x>
<y>295</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -0,0 +1,3 @@
#include "bbplaylistsystem.h"
//this dummy file will trick cmake to compile and link the moc file.

View file

@ -0,0 +1,67 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef BBPLAYLISTSYSTEM_H
#define BBPLAYLISTSYSTEM_H
#include "bbmainwindow.h"
#include <QVariant>
#include <KConfigGroup>
#include <KMainWindow>
#include <KUrl>
class BBPlaylistSystem: public KMainWindow
{
Q_OBJECT
public:
BBPlaylistSystem(int pTabNumber) : mTabNumber(pTabNumber) {}
virtual ~BBPlaylistSystem() {}
virtual void queryNextSong(BBSongQueryJob &pJob) = 0;
virtual void queryPreviousSong(BBSongQueryJob &pJob) = 0;
virtual QVariant currentSong() = 0;
virtual void setCurrentSong(const QVariant &pSong) = 0;
virtual QString displayString(const QVariant &pSong) = 0;
virtual KUrl songUrl(const QVariant &pSong) = 0;
virtual void readSession(KConfigGroup &pConfigGroup) = 0;
virtual void saveSession(KConfigGroup &pConfigGroup) = 0;
virtual void embedControls(QWidget *pControls) = 0;
int tabNumber() {return mTabNumber;}
signals:
void songQueryReady(const BBSongQueryJob &pJob);
protected:
int mTabNumber;
};
enum BBPlaylistRoles
{
BBPathRole = Qt::UserRole + 1, BBFileNameRole,
BBArtistRole, BBTitleRole, BBAlbumRole, BBYearRole, BBLengthRole, BBFileIDRole,
BBIsFolderRole, BBUrlRole, BBAlbumIDRole
};
#endif

View file

@ -0,0 +1,243 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "bbresultview.h"
#include "bbplaylistsystem.h"
#include "bbsetmodel.h"
#include "bblistitem.h"
#include "bbcollectiontab.h"
#include "bbmetainfodialog.h"
#include "bbmainwindow.h"
#include <KLineEdit>
#include <QTreeView>
#include <QLayout>
#include <QPushButton>
#include <QCheckBox>
#include <QPainter>
#include <KLocale>
#include <KAction>
#include <QMenu>
#include <QApplication>
#include <QStyledItemDelegate>
const int gTextMargin = 2;
class BBSongItemDelegate : public QStyledItemDelegate
{
public:
virtual void paint(QPainter *pPainter, const QStyleOptionViewItem &pOption, const QModelIndex &pIndex) const;
virtual QSize sizeHint(const QStyleOptionViewItem &pOption, const QModelIndex &pIndex) const;
};
void BBSongItemDelegate::paint(QPainter *pPainter, const QStyleOptionViewItem &pOption, const QModelIndex &pIndex) const
{
QStyleOptionViewItemV4 lOption = pOption;
initStyleOption(&lOption, pIndex);
QStyle *style = QApplication::style();
pPainter->save();
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &lOption, pPainter);
QRectF r = pOption.rect;
r.adjust(gTextMargin, 0, -gTextMargin, 0);
pPainter->setFont(lOption.font);
if(lOption.state & QStyle::State_Selected)
pPainter->setPen(lOption.palette.color(QPalette::Active, QPalette::HighlightedText));
else
pPainter->setPen(lOption.palette.color(QPalette::Active, QPalette::Text));
if(pIndex.internalPointer() != NULL)
{
QRectF lLengthRect;
pPainter->drawText(r, Qt::AlignVCenter | Qt::AlignRight, pIndex.data(BBLengthRole).toString(), &lLengthRect);
r.setRight(lLengthRect.left() - gTextMargin);
pPainter->drawText(r, Qt::AlignVCenter, pIndex.data(Qt::DisplayRole).toString());
}
else
{
int lOriginalFontSize = lOption.font.pointSize();
lOption.font.setPointSize(lOriginalFontSize + 2);
pPainter->setFont(lOption.font);
QFontMetrics lFontMetric(lOption.font);
QString lArtist = pIndex.data(BBArtistRole).toString();
pPainter->drawText(r.left() + gTextMargin, r.top() + lFontMetric.ascent(), lArtist);
lOption.font.setPointSize(lOriginalFontSize + 1);
pPainter->setFont(lOption.font);
pPainter->drawText(r.left() + lFontMetric.width(lArtist) + 6*gTextMargin, r.top() + lFontMetric.ascent(),
pIndex.data(BBAlbumRole).toString());
}
pPainter->restore();
}
QSize BBSongItemDelegate::sizeHint(const QStyleOptionViewItem &pOption, const QModelIndex &pIndex) const
{
QSize lSize = QStyledItemDelegate::sizeHint(pOption, pIndex);
if(!pIndex.parent().isValid())
{
QFont lFont = pOption.font;
lFont.setPointSize(lFont.pointSize() + 2);
QFontMetrics lFontMetrics(lFont);
lSize.setHeight(lFontMetrics.height());
}
return lSize;
}
BBResultView::BBResultView(BBCollectionTab *pCollectionTab, BBAlbumSongModel *pModel)
: QWidget(pCollectionTab), mModel(pModel), mCollectionTab(pCollectionTab)
{
QVBoxLayout *lLayout = new QVBoxLayout;
mLineEdit = new KLineEdit;
mTreeView = new QTreeView;
QHBoxLayout *lHLayout = new QHBoxLayout;
mResetAllButton = new QPushButton(i18n("Reset All Filters"));
lHLayout->addWidget(mLineEdit);
lHLayout->addWidget(mResetAllButton);
lLayout->addLayout(lHLayout);
lLayout->addWidget(mTreeView);
lLayout->setSpacing(1);
setLayout(lLayout);
mLineEdit->setClearButtonShown(true);
mLineEdit->setClickMessage(i18n("Enter filtering text here..."));
mTreeView->setModel(pModel);
mTreeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
mTreeView->setHeaderHidden(true);
mTreeView->setAlternatingRowColors(true);
mTreeView->setAnimated(true);
mTreeView->setItemDelegate(new BBSongItemDelegate);
mTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
KAction *lEditAction = new KAction(i18n("Edit"), this);
lEditAction->setShortcut(Qt::Key_F2);
lEditAction->setShortcutContext(Qt::WidgetShortcut);
mTreeView->addAction(lEditAction);
connect(lEditAction, SIGNAL(triggered()), SLOT(editFilesTriggered()));
KAction *lEnqueueAction = new KAction(i18n("Add to Queue"), this);
lEnqueueAction->setShortcut(Qt::CTRL + Qt::Key_E);
lEnqueueAction->setShortcutContext(Qt::WidgetShortcut);
mTreeView->addAction(lEnqueueAction);
connect(lEnqueueAction, SIGNAL(triggered()), SLOT(enqueueTriggered()));
mContextMenu = new QMenu(this);
mContextMenu->addAction(lEnqueueAction);
mContextMenu->addAction(lEditAction);
mAutoExpandInProgress = false;
connect(mLineEdit, SIGNAL(textChanged(const QString &)), SLOT(lineEditChanged(const QString &)));
connect(mTreeView, SIGNAL(activated(QModelIndex)), SLOT(itemClicked(QModelIndex)));
connect(mTreeView, SIGNAL(expanded(QModelIndex)), SLOT(itemExpanded(QModelIndex)));
connect(mTreeView, SIGNAL(collapsed(QModelIndex)), SLOT(itemCollapsed(QModelIndex)));
connect(mTreeView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(showPopupMenu(QPoint)));
connect(mResetAllButton, SIGNAL(clicked()), mCollectionTab, SLOT(resetAllFilters()));
}
void BBResultView::lineEditChanged(const QString &)
{
mCollectionTab->refreshAllExcept(NULL);
}
QSet<BBSongListItem> BBResultView::filesToUpdate()
{
QSet<BBSongListItem> lSet;
QItemSelectionModel *lSelectionModel = mTreeView->selectionModel();
if(lSelectionModel->hasSelection())
{
QModelIndexList lIndexes = lSelectionModel->selectedIndexes();
foreach(QModelIndex i, lIndexes)
{
if(i.parent().isValid()) //only song items, not albums
lSet.insert(mModel->findSongItem(i));
}
}
else
{
lSet = mModel->allSongs();
}
return lSet;
}
void BBResultView::itemClicked(QModelIndex pIndex)
{
if(pIndex.parent().isValid())
{
gMainWindow->setCurrentPlaylistSystem(mCollectionTab->tabNumber());
gMainWindow->setCurrentSong(pIndex.data(BBFileIDRole));
}
}
void BBResultView::scrollToNewSong(const QModelIndex &pIndexCurrent, const QModelIndex &pIndexPrevious)
{
mTreeView->setAnimated(false);
QModelIndex lPreviousAlbum(pIndexPrevious.parent());
if(lPreviousAlbum != pIndexCurrent.parent() && !mModel->isManuallyExpanded(lPreviousAlbum))
mTreeView->collapse(lPreviousAlbum);
mAutoExpandInProgress = true;
mTreeView->scrollTo(pIndexCurrent);
mAutoExpandInProgress = false;
mTreeView->setAnimated(true);
}
void BBResultView::itemExpanded(const QModelIndex &pIndex)
{
if(!mAutoExpandInProgress)
mModel->setManuallyExpanded(pIndex, true);
}
void BBResultView::itemCollapsed(const QModelIndex &pIndex)
{
mModel->setManuallyExpanded(pIndex, false);
}
void BBResultView::showPopupMenu(const QPoint &pPoint)
{
QModelIndex lIndex = mTreeView->indexAt(pPoint);
if(lIndex.isValid() && lIndex.parent().isValid())
mContextMenu->popup(mTreeView->mapToGlobal(pPoint));
}
void BBResultView::editFilesTriggered()
{
QItemSelectionModel *lSelectionModel = mTreeView->selectionModel();
QModelIndex lIndex = lSelectionModel->currentIndex();
if(!lIndex.isValid() || !lIndex.parent().isValid())
return;
BBMetaInfoDialog *lDialog = new BBMetaInfoDialog(mCollectionTab, mCollectionTab);
connect(lDialog, SIGNAL(finished(int)), lDialog, SLOT(deleteLater()));
if(lDialog->fillInValues(lIndex))
lDialog->show();
}
void BBResultView::enqueueTriggered()
{
QItemSelectionModel *lSelectionModel = mTreeView->selectionModel();
QModelIndex lIndex = lSelectionModel->currentIndex();
if(!lIndex.isValid() || !lIndex.parent().isValid())
return;
QString lDisplayText = QString("%1 - %2").arg(lIndex.data(BBArtistRole).toString(), lIndex.data(BBTitleRole).toString());
gMainWindow->addToPlayQueue(lIndex.data(BBFileIDRole), lDisplayText, mCollectionTab->tabNumber());
}

View file

@ -0,0 +1,64 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef BBRESULTVIEW_H
#define BBRESULTVIEW_H
#include <QCheckBox>
#include <QModelIndex>
#include <KDE/KLineEdit>
class QTreeView;
class QPushButton;
class QMenu;
class BBCollectionTab;
class BBAlbumSongModel;
class BBSongListItem;
class BBResultView : public QWidget
{
Q_OBJECT
public:
BBResultView(BBCollectionTab *pCollectionTab, BBAlbumSongModel *pModel);
QString filterText() {return mLineEdit->text();}
QSet<BBSongListItem> filesToUpdate();
void scrollToNewSong(const QModelIndex &pIndexCurrent, const QModelIndex &pIndexPrevious);
protected slots:
void lineEditChanged(const QString &pNewText);
void itemClicked(QModelIndex pIndex);
void itemExpanded(const QModelIndex &pIndex);
void itemCollapsed(const QModelIndex &pIndex);
void showPopupMenu(const QPoint &pPoint);
void editFilesTriggered();
void enqueueTriggered();
protected:
QTreeView *mTreeView;
KLineEdit *mLineEdit;
QPushButton *mResetAllButton;
BBAlbumSongModel *mModel;
BBCollectionTab *mCollectionTab;
QMenu *mContextMenu;
bool mAutoExpandInProgress;
};
#endif // BBRESULTVIEW_H

552
boombox/src/bbsetmodel.cpp Normal file
View file

@ -0,0 +1,552 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "bbsetmodel.h"
#include "bbcollectiontab.h"
#include <QtAlgorithms>
#include <KLocale>
#include <KRandom>
#include <QFont>
QVariant BBStringSetModel::data(const QModelIndex &pIndex, int pRole) const
{
if(!pIndex.isValid())
return QVariant();
if(pIndex.row() >= mList.size())
return QVariant();
if(pRole == Qt::DisplayRole)
return mList.at(pIndex.row());
else
return QVariant();
}
void BBStringSetModel::setNewSet(const QSet<BBStringListItem> &pNewSet)
{
QList<BBStringListItem> lOldSongs = (mSet - pNewSet).toList();
qSort(lOldSongs.begin(), lOldSongs.end(), qGreater<BBStringListItem>());
int lStartPos = -1, lEndPos = -1;
QListIterator<BBStringListItem> i(lOldSongs);
if(i.hasNext())
lStartPos = lEndPos = i.next().mPosition;
while(i.hasNext())
{
int lNextPos = i.next().mPosition;
if(lNextPos == lStartPos - 1)
lStartPos--;
else
{
beginRemoveRows(QModelIndex(), lStartPos, lEndPos);
for(int j = lEndPos; j>=lStartPos; --j)
mList.removeAt(j);
endRemoveRows();
lStartPos = lEndPos = lNextPos;
}
}
if(lStartPos >= 0)
{
beginRemoveRows(QModelIndex(), lStartPos, lEndPos);
for(int j = lEndPos; j>=lStartPos; --j)
mList.removeAt(j);
endRemoveRows();
}
QList<BBStringListItem> lNewSongs = (pNewSet - mSet).toList();
qSort(lNewSongs.begin(), lNewSongs.end());
lStartPos = -1, lEndPos = -1;
i = lNewSongs;
if(i.hasNext())
lStartPos = lEndPos = i.next().mPosition;
while(i.hasNext())
{
int lNextPos = i.next().mPosition;
if(lNextPos == lEndPos + 1)
lEndPos++;
else
{
beginInsertRows(QModelIndex(), lStartPos, lEndPos);
for(int j = lStartPos; j<=lEndPos; ++j)
mList.insert(j, lNewSongs.takeFirst().mString);
endInsertRows();
lStartPos = lEndPos = lNextPos;
}
}
if(lStartPos >= 0)
{
beginInsertRows(QModelIndex(), lStartPos, lEndPos);
for(int j = lStartPos; j<=lEndPos; ++j)
mList.insert(j, lNewSongs.takeFirst().mString);
endInsertRows();
}
mSet = pNewSet;
}
int BBAlbumSongModel::rowCount(const QModelIndex & pParent) const
{
if(!pParent.isValid())
return mAlbumList.count();
else
{
if(pParent.internalPointer() != NULL || pParent.row() >= mAlbumList.count() || pParent.row() < 0)
return 0;
return mAlbumList.at(pParent.row()).mSongs.count();
}
}
QVariant BBAlbumSongModel::data(const QModelIndex &pIndex, int pRole) const
{
if(!pIndex.isValid() || pIndex.column() != 0)
return QVariant();
if(pIndex.internalPointer() == NULL) //this is an album item
{
if(pIndex.row() >= mAlbumList.size() || pIndex.row() < 0)
return QVariant();
const BBAlbumData &lData = mAlbumList.at(pIndex.row());
switch(pRole)
{
case Qt::FontRole:
if(lData.mAlbumID == mCurrentlyPlayingAlbumID)
{
QFont lFont = mCollectionTab->font();
lFont.setBold(true);
return lFont;
}
else
return QVariant();
case BBAlbumRole:
return lData.mAlbum;
case BBArtistRole:
return lData.mArtist;
case BBPathRole:
return lData.mFolderPath;
default:
return QVariant();
}
}
else // this is a song item
{
BBAlbumData *lAlbumData = (BBAlbumData *) pIndex.internalPointer();
if(pIndex.row() >= lAlbumData->mSongs.count())
return QVariant();
const BBSongData &lSongData = lAlbumData->mSongs.at(pIndex.row());
switch(pRole)
{
case Qt::FontRole:
if(lSongData.mFileID == mCurrentlyPlayingSongID)
{
QFont lFont = mCollectionTab->font();
lFont.setBold(true);
return lFont;
}
else
return QVariant();
case Qt::DisplayRole:
if(lAlbumData->mIsVA)
return QString("%1 - %2").arg(lSongData.mArtist, lSongData.mTitle);
case BBTitleRole: //fall through
return lSongData.mTitle;
case BBArtistRole:
if(lAlbumData->mIsVA)
return lSongData.mArtist;
else
return lAlbumData->mArtist;
case BBPathRole:
return lAlbumData->mFolderPath;
case BBFileNameRole:
return lSongData.mFileName;
case BBLengthRole:
return lSongData.mLength;
case BBFileIDRole:
return lSongData.mFileID;
case BBAlbumIDRole:
return lAlbumData->mAlbumID;
default:
return QVariant();
}
}
}
QModelIndex BBAlbumSongModel::index(int pRow, int pColumn, const QModelIndex &pParent) const
{
if(!pParent.isValid()) //this is an album item
{
if(pRow >= mAlbumList.count() || pRow < 0 || pColumn != 0)
return QModelIndex();
return createIndex(pRow, pColumn, (void *)NULL);
}
else //this is a song item
{
if(pParent.row() >= mAlbumList.count())
return QModelIndex();
const BBAlbumData &lAlbumData = mAlbumList.at(pParent.row());
if(pRow >= lAlbumData.mSongs.count() || pRow < 0 || pColumn != 0)
return QModelIndex();
return createIndex(pRow, pColumn, (void *)&lAlbumData);
}
}
QModelIndex BBAlbumSongModel::parent(const QModelIndex &pIndex) const
{
if(!pIndex.isValid() || pIndex.internalPointer() == NULL)
return QModelIndex();
else
{
BBAlbumData *lAlbumData = (BBAlbumData *)pIndex.internalPointer();
return createIndex(findAlbumRow(lAlbumData->mAlbumID), 0, (void *)NULL);
}
}
void BBAlbumSongModel::setNewSongs(const QSet<BBSongListItem> &pNewSongs, const QSet<BBAlbumListItem> &pNewAlbums)
{
//remove songs to be updated now, added again later in this method.
QList<BBSongListItem> lOldSongs = (mSongSet - pNewSongs + mSongsToUpdate).toList();
// sort in reverse order so that removal starts in end of list, keeps indexes correct.
qSort(lOldSongs.begin(), lOldSongs.end(), qGreater<BBSongListItem>());
int lStartPos = -1, lEndPos = -1, lCurrentAlbumPos = -1;
QListIterator<BBSongListItem> i(lOldSongs);
if(i.hasNext())
{
const BBSongListItem &lNext = i.next();
lStartPos = lEndPos = lNext.mIndex.mSongPos;
lCurrentAlbumPos = lNext.mIndex.mAlbumPos;
}
while(i.hasNext())
{
const BBSongListItem &lNext = i.next();
int lNextPos = lNext.mIndex.mSongPos;
int lNextAlbumPos = lNext.mIndex.mAlbumPos;
if(lNextPos == lStartPos - 1 && lCurrentAlbumPos == lNextAlbumPos)
lStartPos--;
else
{
beginRemoveRows(createIndex(lCurrentAlbumPos, 0, (void *)NULL), lStartPos, lEndPos);
for(int j = lEndPos; j>=lStartPos; --j)
mAlbumList[lCurrentAlbumPos].mSongs.removeAt(j);
endRemoveRows();
lStartPos = lEndPos = lNextPos;
lCurrentAlbumPos = lNextAlbumPos;
}
}
if(lStartPos >= 0)
{
beginRemoveRows(createIndex(lCurrentAlbumPos, 0, (void *)NULL), lStartPos, lEndPos);
for(int j = lEndPos; j>=lStartPos; --j)
mAlbumList[lCurrentAlbumPos].mSongs.removeAt(j);
endRemoveRows();
}
QList<BBAlbumListItem> lOldAlbums = (mAlbumSet - pNewAlbums).toList();
// sort in reverse order so that removal starts in end of list, keeps indexes correct.
qSort(lOldAlbums.begin(), lOldAlbums.end(), qGreater<BBAlbumListItem>());
lStartPos = -1;
lEndPos = -1;
QListIterator<BBAlbumListItem> i2(lOldAlbums);
if(i2.hasNext())
lStartPos = lEndPos = i2.next().mPosition;
while(i2.hasNext())
{
int lNextPos = i2.next().mPosition;
if(lNextPos == lStartPos - 1)
lStartPos--;
else
{
beginRemoveRows(QModelIndex(), lStartPos, lEndPos);
for(int j = lEndPos; j>=lStartPos; --j)
mAlbumList.removeAt(j);
endRemoveRows();
lStartPos = lEndPos = lNextPos;
}
}
if(lStartPos >= 0)
{
beginRemoveRows(QModelIndex(),lStartPos, lEndPos);
for(int j = lEndPos; j>=lStartPos; --j)
mAlbumList.removeAt(j);
endRemoveRows();
}
//removing updated songs from sets here will cause them to be added
mSongSet -= mSongsToUpdate;
QList<BBAlbumListItem> lNewAlbums = (pNewAlbums- mAlbumSet).toList();
qSort(lNewAlbums.begin(), lNewAlbums.end());
lStartPos = -1;
lEndPos = -1;
i2 = lNewAlbums;
if(i2.hasNext())
lStartPos = lEndPos = i2.next().mPosition;
while(i2.hasNext())
{
int lNextPos = i2.next().mPosition;
if(lNextPos == lEndPos + 1)
lEndPos++;
else
{
beginInsertRows(QModelIndex(), lStartPos, lEndPos);
for(int j = lStartPos; j<=lEndPos; ++j)
mAlbumList.insert(j, lNewAlbums.takeFirst().mData);
endInsertRows();
lStartPos = lEndPos = lNextPos;
}
}
if(lStartPos >= 0)
{
beginInsertRows(QModelIndex(), lStartPos, lEndPos);
for(int j = lStartPos; j<=lEndPos; ++j)
mAlbumList.insert(j, lNewAlbums.takeFirst().mData);
endInsertRows();
}
QList<BBSongListItem> lNewSongs = (pNewSongs - mSongSet).toList();
qSort(lNewSongs.begin(), lNewSongs.end());
lStartPos = -1;
lEndPos = -1;
lCurrentAlbumPos = -1;
i = lNewSongs;
if(i.hasNext())
{
const BBSongListItem &lNext = i.next();
lStartPos = lEndPos = lNext.mIndex.mSongPos;
lCurrentAlbumPos = lNext.mIndex.mAlbumPos;
}
while(i.hasNext())
{
const BBSongListItem &lNext = i.next();
int lNextPos = lNext.mIndex.mSongPos;
int lNextAlbumPos = lNext.mIndex.mAlbumPos;
if(lNextPos == lEndPos + 1 && lCurrentAlbumPos == lNextAlbumPos)
lEndPos++;
else
{
beginInsertRows(createIndex(lCurrentAlbumPos, 0, (void *)NULL), lStartPos, lEndPos);
for(int j = lStartPos; j<=lEndPos; ++j)
mAlbumList[lCurrentAlbumPos].mSongs.insert(j, lNewSongs.takeFirst().mData);
endInsertRows();
lStartPos = lEndPos = lNextPos;
lCurrentAlbumPos = lNextAlbumPos;
}
}
if(lStartPos >= 0)
{
beginInsertRows(createIndex(lCurrentAlbumPos, 0, (void *)NULL), lStartPos, lEndPos);
for(int j = lStartPos; j<=lEndPos; ++j)
mAlbumList[lCurrentAlbumPos].mSongs.insert(j, lNewSongs.takeFirst().mData);
endInsertRows();
}
mAlbumSet = pNewAlbums;
mSongSet = pNewSongs;
shuffle();
}
void BBAlbumSongModel::shuffle()
{
if(mSongSet.isEmpty())
{
mLastShuffleItem = mFirstShuffleItem = BBIndex();
return;
}
QList<BBIndex> lTempList;
for(int i = 0; i < mAlbumList.count(); ++i)
{
int max = mAlbumList.at(i).mSongs.count();
for(int j = 0; j < max; ++j)
lTempList.append(BBIndex(i, j));
}
BBIndex lRandomPos = lTempList.takeAt(KRandom::random() % lTempList.count());
mLastShuffleItem = mFirstShuffleItem = lRandomPos;
findSongData(mFirstShuffleItem).mPrevShuffled = BBIndex();
while(!lTempList.isEmpty())
{
lRandomPos = lTempList.takeAt(KRandom::random() % lTempList.count());
findSongData(mLastShuffleItem).mNextShuffled = lRandomPos;
findSongData(lRandomPos).mPrevShuffled = mLastShuffleItem;
mLastShuffleItem = lRandomPos;
}
findSongData(mLastShuffleItem).mNextShuffled = BBIndex();
}
QPersistentModelIndex BBAlbumSongModel::nextLinear(const QPersistentModelIndex &pIndex)
{
if(!pIndex.isValid())
return QModelIndex();
BBAlbumData *lAlbumData = (BBAlbumData *)pIndex.internalPointer();
int lNextSongPos = pIndex.row() + 1;
if(lNextSongPos >= lAlbumData->mSongs.count())
{
lNextSongPos = 0;
int lNextAlbumPos = findAlbumRow(lAlbumData->mAlbumID) + 1;
if(lNextAlbumPos >= mAlbumList.count())
return QModelIndex();
lAlbumData = (BBAlbumData *) &mAlbumList.at(lNextAlbumPos);
}
return createIndex(lNextSongPos, 0, (void *)lAlbumData);
}
QPersistentModelIndex BBAlbumSongModel::nextShuffled(const QPersistentModelIndex &pIndex)
{
if(!pIndex.isValid())
return QModelIndex();
BBAlbumData *lAlbumData = (BBAlbumData *)pIndex.internalPointer();
BBIndex lNextPos = lAlbumData->mSongs.at(pIndex.row()).mNextShuffled;
if(!lNextPos.isValid())
return QModelIndex();
return createIndex(lNextPos.mSongPos, 0, (void *)&mAlbumList.at(lNextPos.mAlbumPos));
}
QPersistentModelIndex BBAlbumSongModel::prevLinear(const QPersistentModelIndex &pIndex)
{
if(!pIndex.isValid())
return QModelIndex();
BBAlbumData *lAlbumData = (BBAlbumData *)pIndex.internalPointer();
int lNextSongPos = pIndex.row() - 1;
if(lNextSongPos < 0)
{
int lNextAlbumPos = findAlbumRow(lAlbumData->mAlbumID) - 1;
if(lNextAlbumPos < 0)
return QModelIndex();
lAlbumData = (BBAlbumData *) &mAlbumList.at(lNextAlbumPos);
lNextSongPos = lAlbumData->mSongs.count() - 1;
}
return createIndex(lNextSongPos, 0, (void *)lAlbumData);
}
QPersistentModelIndex BBAlbumSongModel::prevShuffled(const QPersistentModelIndex &pIndex)
{
if(!pIndex.isValid())
return QModelIndex();
BBAlbumData *lAlbumData = (BBAlbumData *)pIndex.internalPointer();
BBIndex lNextPos = lAlbumData->mSongs.at(pIndex.row()).mPrevShuffled;
if(!lNextPos.isValid())
return QModelIndex();
return createIndex(lNextPos.mSongPos, 0, (void *)&mAlbumList.at(lNextPos.mAlbumPos));
}
QPersistentModelIndex BBAlbumSongModel::firstLinear()
{
if(mSongSet.isEmpty())
return QModelIndex();
return createIndex(0, 0, (void *)&mAlbumList.first());
}
QPersistentModelIndex BBAlbumSongModel::firstShuffled()
{
if(!mFirstShuffleItem.isValid())
return QModelIndex();
return createIndex(mFirstShuffleItem.mSongPos, 0, (void *)&mAlbumList.at(mFirstShuffleItem.mAlbumPos));
}
QPersistentModelIndex BBAlbumSongModel::lastLinear()
{
if(mSongSet.isEmpty())
return QModelIndex();
int lSongPos = mAlbumList.last().mSongs.count() - 1;
return createIndex(lSongPos, 0, (void *)&mAlbumList.last());
}
QPersistentModelIndex BBAlbumSongModel::lastShuffled()
{
if(!mLastShuffleItem.isValid())
return QModelIndex();
return createIndex(mLastShuffleItem.mSongPos, 0, (void *)&mAlbumList.at(mLastShuffleItem.mAlbumPos));
}
void BBAlbumSongModel::setCurrentSong(int pCurrentSong, int pCurrentAlbum, QModelIndex pPreviousIndex)
{
mCurrentlyPlayingSongID = pCurrentSong;
mCurrentlyPlayingAlbumID = pCurrentAlbum;
QModelIndex lCurrentIndex = indexForFileID(pCurrentSong);
if(lCurrentIndex.isValid() && lCurrentIndex.column() == 0 && lCurrentIndex.internalPointer() != NULL)
{
emit dataChanged(lCurrentIndex, lCurrentIndex);
QModelIndex lAlbumIndex = lCurrentIndex.parent();
emit dataChanged(lAlbumIndex, lAlbumIndex);
}
if(pPreviousIndex.isValid() && pPreviousIndex.column() == 0 && pPreviousIndex.internalPointer() != NULL)
{
emit dataChanged(pPreviousIndex, pPreviousIndex);
QModelIndex lAlbumIndex = pPreviousIndex.parent();
emit dataChanged(lAlbumIndex, lAlbumIndex);
}
}
void BBAlbumSongModel::setManuallyExpanded(const QModelIndex &pIndex, bool pManuallyExpanded)
{
if(!pIndex.isValid() || pIndex.column() != 0 || pIndex.internalPointer() != NULL)
return;
if(pIndex.row() >= mAlbumList.count())
return;
mAlbumList[pIndex.row()].mManuallyExpanded = pManuallyExpanded;
}
bool BBAlbumSongModel::isManuallyExpanded(const QModelIndex &pIndex)
{
if(!pIndex.isValid() || pIndex.column() != 0 || pIndex.internalPointer() != NULL)
return false;
if(pIndex.row() >= mAlbumList.count())
return false;
return mAlbumList[pIndex.row()].mManuallyExpanded;
}
int BBAlbumSongModel::findAlbumRow(int pAlbumID) const
{
BBAlbumListItem lItem;
lItem.mData.mAlbumID = pAlbumID;
QSet<BBAlbumListItem>::const_iterator lIter = mAlbumSet.find(lItem);
return (*lIter).mPosition;
}
BBSongListItem BBAlbumSongModel::findSongItem(QModelIndex pIndex)
{
BBSongListItem lItem;
lItem.mData.mFileID = pIndex.data(BBFileIDRole).toInt();
QSet<BBSongListItem>::const_iterator lIter = mSongSet.find(lItem);
return *lIter;
}
QModelIndex BBAlbumSongModel::indexForFileID(int pFileID)
{
BBSongListItem lItem;
lItem.mData.mFileID = pFileID;
QSet<BBSongListItem>::const_iterator lIter = mSongSet.find(lItem);
if(lIter == mSongSet.constEnd())
return QModelIndex();
else
return index((*lIter).mIndex.mSongPos, 0, index((*lIter).mIndex.mAlbumPos));
}

120
boombox/src/bbsetmodel.h Normal file
View file

@ -0,0 +1,120 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef BBSETMODEL_H
#define BBSETMODEL_H
#include <QAbstractItemModel>
#include <QAbstractListModel>
#include <QSet>
#include <QStringList>
#include "bblistitem.h"
class BBCollectionTab;
class BBStringSetModel : public QAbstractListModel
{
public:
BBStringSetModel(QObject *pParent)
: QAbstractListModel(pParent)
{}
virtual int rowCount(const QModelIndex & /*parent = QModelIndex()*/ ) const
{ return mList.count();}
virtual QVariant data(const QModelIndex &pIndex, int pRole = Qt::DisplayRole) const;
void setNewSet(const QSet<BBStringListItem> &pNewSet);
int findStringPos(const QString &pString)
{
return mList.indexOf(pString);
}
protected:
QSet<BBStringListItem> mSet;
QStringList mList;
};
class BBAlbumSongModel : public QAbstractItemModel
{
Q_OBJECT
public:
BBAlbumSongModel(QObject *pParent, BBCollectionTab *pCollectionTab)
: QAbstractItemModel(pParent), mLastShuffleItem(), mFirstShuffleItem(),
mCurrentlyPlayingSongID(-1), mCurrentlyPlayingAlbumID(-1), mCollectionTab(pCollectionTab)
{}
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const
{
Q_UNUSED(section);
Q_UNUSED(orientation);
Q_UNUSED(role);
return QVariant();
}
virtual int columnCount(const QModelIndex &pParent = QModelIndex()) const
{
Q_UNUSED(pParent);
return 1;
}
virtual QModelIndex index(int pRow, int pColumn = 0, const QModelIndex &pParent = QModelIndex()) const;
virtual QModelIndex parent(const QModelIndex & pIndex) const;
virtual int rowCount(const QModelIndex & pParent = QModelIndex()) const;
virtual QVariant data(const QModelIndex &pIndex, int pRole = Qt::DisplayRole) const;
void setCurrentSong(int pCurrentSong, int pCurrentAlbum, QModelIndex pPreviousIndex);
void updateSongs(const QSet<BBSongListItem> &pSongsToUpdate) { mSongsToUpdate = pSongsToUpdate; }
void setManuallyExpanded(const QModelIndex &pIndex, bool pManuallyExpanded);
bool isManuallyExpanded(const QModelIndex &pIndex);
QSet<BBSongListItem> allSongs() {return mSongSet;}
BBSongListItem findSongItem(QModelIndex pIndex);
QModelIndex indexForFileID(int pFileID);
QPersistentModelIndex nextLinear(const QPersistentModelIndex &pIndex);
QPersistentModelIndex nextShuffled(const QPersistentModelIndex &pIndex);
QPersistentModelIndex prevLinear(const QPersistentModelIndex &pIndex);
QPersistentModelIndex prevShuffled(const QPersistentModelIndex &pIndex);
QPersistentModelIndex firstLinear();
QPersistentModelIndex firstShuffled();
QPersistentModelIndex lastLinear();
QPersistentModelIndex lastShuffled();
public slots:
void setNewSongs(const QSet<BBSongListItem> &pNewSongs, const QSet<BBAlbumListItem> &pNewAlbums);
protected:
int findAlbumRow(int pAlbumID) const;
BBSongData &findSongData(const BBIndex &pIndex)
{
return mAlbumList[pIndex.mAlbumPos].mSongs[pIndex.mSongPos];
}
void shuffle();
QSet<BBSongListItem> mSongSet;
QSet<BBAlbumListItem> mAlbumSet;
QList<BBAlbumData> mAlbumList;
BBIndex mLastShuffleItem, mFirstShuffleItem;
int mCurrentlyPlayingSongID, mCurrentlyPlayingAlbumID;
BBCollectionTab *mCollectionTab;
QSet<BBSongListItem> mSongsToUpdate;
};
#endif

View file

@ -0,0 +1,7 @@
# Code generation options for kconfig_compiler
File=boombox.kcfg
ClassName=BBSettings
Singleton=true
Mutators=true
ItemAccessors=true
SetUserTexts=true

View file

@ -0,0 +1,53 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "bbstreameditform.h"
#include "ui_bbstreameditform.h"
BBStreamEditForm::BBStreamEditForm(const QString &pName, const QString &pUrl, QWidget *parent)
:QDialog(parent), ui(new Ui::BBStreamEditForm)
{
ui->setupUi(this);
ui->nameEdit->setText(pName);
ui->urlEdit->setText(pUrl);
}
BBStreamEditForm::~BBStreamEditForm()
{
delete ui;
}
void BBStreamEditForm::changeEvent(QEvent *e)
{
QDialog::changeEvent(e);
switch (e->type()) {
case QEvent::LanguageChange:
ui->retranslateUi(this);
break;
default:
break;
}
}
void BBStreamEditForm::accept()
{
mName = ui->nameEdit->text();
mUrl = ui->urlEdit->text();
QDialog::accept();
}

View file

@ -0,0 +1,50 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef BBSTREAMEDITFORM_H
#define BBSTREAMEDITFORM_H
#include <QDialog>
namespace Ui {
class BBStreamEditForm;
}
class BBStreamEditForm : public QDialog
{
Q_OBJECT
public:
explicit BBStreamEditForm(const QString &pName, const QString &pUrl, QWidget *parent = 0);
~BBStreamEditForm();
QString mName;
QString mUrl;
public slots:
virtual void accept();
protected:
void changeEvent(QEvent *e);
private:
Ui::BBStreamEditForm *ui;
};
#endif // BBSTREAMEDITFORM_H

View file

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BBStreamEditForm</class>
<widget class="QDialog" name="BBStreamEditForm">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>182</width>
<height>89</height>
</rect>
</property>
<property name="windowTitle">
<string>Internet Stream</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Please provide details for the stream:</string>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<item row="2" column="1">
<widget class="QLineEdit" name="nameEdit"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="urlEdit"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>URL:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>BBStreamEditForm</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>BBStreamEditForm</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -0,0 +1,719 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "bbstreamstab.h"
#include "bbsettings.h"
#include "bbstreameditform.h"
#include <QApplication>
#include <QDebug>
#include <QFile>
#include <QLayout>
#include <QMimeData>
#include <QTreeView>
#include <KAction>
#include <KActionCollection>
#include <KInputDialog>
#include <KStandardAction>
#include <KToolBar>
Q_DECLARE_METATYPE(QPersistentModelIndex)
class BBStreamItem
{
public:
explicit BBStreamItem(BBStreamItem *pParent, int pRow, QDomElement &pElement);
~BBStreamItem();
BBStreamItem *mParent;
int mRow;
QDomElement mElement;
QList<BBStreamItem *> mChildren;
};
BBStreamItem::BBStreamItem(BBStreamItem *pParent, int pRow, QDomElement &pElement)
: mParent(pParent), mRow(pRow), mElement(pElement)
{
int lCurrentRow = 0;
QDomElement lCurrent = pElement.firstChildElement();
for(; !lCurrent.isNull(); lCurrent = lCurrent.nextSiblingElement())
mChildren.append(new BBStreamItem(this, lCurrentRow++, lCurrent));
}
BBStreamItem::~BBStreamItem()
{
foreach(BBStreamItem *lCurrent, mChildren)
delete lCurrent;
}
BBStreamModel::BBStreamModel(const QString &pFileName, QObject *pParent)
: QAbstractItemModel(pParent), mFileName(pFileName)
{
QFile lXmlFile(pFileName);
if(!lXmlFile.exists() || !lXmlFile.open(QIODevice::ReadOnly) || !mDocument.setContent(&lXmlFile))
{
lXmlFile.close();
if(!lXmlFile.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate))
return;
QTextStream lTextStream(&lXmlFile);
lTextStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << endl;
lTextStream << "<boombox_streams_db>" << endl;
lTextStream << "<folder name=\"Example Streams\">" << endl;
lTextStream << "\t<entry href=\"http://scfire-dtc-aa01.stream.aol.com:80/stream/1065\" name=\"Digitally Imported - Vocal Trance\"></entry>" << endl;
lTextStream << "\t<entry href=\"http://tai-03.egihosting.com/amstrance-64k-aac\" name=\"1.FM - Amsterdam Trance Radio\"></entry>" << endl;
lTextStream << "\t<entry href=\"http://stream.pulsradio.com:5000\" name=\"PulsRadio - www.pulsradio.com\"></entry>" << endl;
lTextStream << "\t<entry href=\"http://m1live-shoutcast.returning.net:8000\" name=\"MusicOne\"></entry>" << endl;
lTextStream << "</folder>" << endl;
lTextStream << "</boombox_streams_db>" << endl;
lXmlFile.seek(0);
if(!mDocument.setContent(&lXmlFile))
{
qWarning() <<"Giving up getting a valid XML file.";
return;
}
}
QDomElement lDocElement = mDocument.documentElement();
mRoot = new BBStreamItem(0, 0, lDocElement);
}
BBStreamModel::~BBStreamModel()
{
delete mRoot;
}
QModelIndex BBStreamModel::index(int pRow, int pColumn, const QModelIndex &pParent) const
{
BBStreamItem *lParentItem;
if(!pParent.isValid())
lParentItem = mRoot;
else
lParentItem = static_cast<BBStreamItem *>(pParent.internalPointer());
if(pRow < 0 || pRow >= lParentItem->mChildren.count() || pColumn != 0)
return QModelIndex();
else
return createIndex(pRow, pColumn, lParentItem->mChildren.at(pRow));
}
QModelIndex BBStreamModel::parent(const QModelIndex &pChild) const
{
if(!pChild.isValid())
return QModelIndex();
else
{
BBStreamItem *lChildItem = static_cast<BBStreamItem *>(pChild.internalPointer());
if(lChildItem->mParent == mRoot)
return QModelIndex();
else
return createIndex(lChildItem->mParent->mRow, 0, lChildItem->mParent);
}
}
int BBStreamModel::rowCount(const QModelIndex &pParent) const
{
BBStreamItem *lParentItem;
if(!pParent.isValid())
lParentItem = mRoot;
else
lParentItem = static_cast<BBStreamItem *>(pParent.internalPointer());
return lParentItem->mChildren.count();
}
int BBStreamModel::columnCount(const QModelIndex &pParent) const
{
Q_UNUSED(pParent)
return 1;
}
QVariant BBStreamModel::data(const QModelIndex &pIndex, int pRole) const
{
if(!pIndex.isValid())
return QVariant();
BBStreamItem *lStreamItem = static_cast<BBStreamItem *>(pIndex.internalPointer());
QString lType = lStreamItem->mElement.tagName();
if(lType == "folder")
{
switch(pRole)
{
case Qt::DisplayRole:
return lStreamItem->mElement.attribute("name");
case BBIsFolderRole:
return true;
case Qt::FontRole:
if(mCurrentSong.parent() == pIndex)
{
QFont lFont = QApplication::font();
lFont.setBold(true);
return lFont;
}
else
return QVariant();
}
}
else if(lType == "entry")
{
switch(pRole)
{
case Qt::DisplayRole:
return lStreamItem->mElement.attribute("name");
case BBIsFolderRole:
return false;
case BBUrlRole:
return lStreamItem->mElement.attribute("href");
case Qt::FontRole:
if(mCurrentSong == pIndex)
{
QFont lFont = QApplication::font();
lFont.setBold(true);
return lFont;
}
else
return QVariant();
}
}
return QVariant();
}
QModelIndex BBStreamModel::createNewEntry(const QModelIndex &pParent, const QString &pName, const QString &pUrl, int pRow)
{
QDomElement lNewEntry = mDocument.createElement("entry");
lNewEntry.setAttribute("name", pName);
lNewEntry.setAttribute("href", pUrl);
BBStreamItem *lParentItem;
QModelIndex lParentIndex = pParent;
if(!lParentIndex.isValid())
lParentItem = mRoot;
else
{
lParentItem = static_cast<BBStreamItem *>(pParent.internalPointer());
if(lParentItem->mElement.tagName() == "entry")
{
lParentItem = lParentItem->mParent;
lParentIndex = lParentIndex.parent();
}
}
int lRow = (pRow == -1 ? rowCount(lParentIndex): pRow);
beginInsertRows(lParentIndex, lRow, lRow);
if(pRow == -1)
{
lParentItem->mElement.appendChild(lNewEntry);
lParentItem->mChildren.append(new BBStreamItem(lParentItem, lRow, lNewEntry));
}
else
{
lParentItem->mElement.insertBefore(lNewEntry, lParentItem->mElement.childNodes().item(lRow));
lParentItem->mChildren.insert(lRow, new BBStreamItem(lParentItem, lRow, lNewEntry));
for(int i = lRow + 1; i < lParentItem->mChildren.count(); ++i)
lParentItem->mChildren[i]->mRow++;
}
endInsertRows();
return index(lRow, 0, lParentIndex);
}
void BBStreamModel::editEntry(const QModelIndex &pEntry, const QString &pName, const QString &pUrl)
{
if(!pEntry.isValid())
return;
BBStreamItem *lItem = static_cast<BBStreamItem *>(pEntry.internalPointer());
if(!pName.isEmpty())
lItem->mElement.setAttribute("name", pName);
if(!pUrl.isEmpty())
lItem->mElement.setAttribute("href", pUrl);
}
void BBStreamModel::deleteEntry(const QModelIndex &pEntry)
{
if(!pEntry.isValid())
return;
BBStreamItem *lItem = static_cast<BBStreamItem *>(pEntry.internalPointer());
beginRemoveRows(pEntry.parent(), lItem->mRow, lItem->mRow);
lItem->mParent->mChildren.removeAt(lItem->mRow);
lItem->mParent->mElement.removeChild(lItem->mElement);
for(int i = lItem->mRow; i < lItem->mParent->mChildren.count(); ++i)
lItem->mParent->mChildren[i]->mRow--;
delete lItem;
endRemoveRows();
}
QModelIndex BBStreamModel::createNewFolder(const QModelIndex &pParent, const QString &pName, int pRow)
{
QDomElement lNewEntry = mDocument.createElement("folder");
lNewEntry.setAttribute("name", pName);
BBStreamItem *lParentItem;
QModelIndex lParentIndex = pParent;
if(!lParentIndex.isValid())
lParentItem = mRoot;
else
{
lParentItem = static_cast<BBStreamItem *>(pParent.internalPointer());
if(lParentItem->mElement.tagName() == "entry")
{
lParentItem = lParentItem->mParent;
lParentIndex = lParentIndex.parent();
}
}
int lRow = (pRow == -1 ? rowCount(lParentIndex) : pRow);
beginInsertRows(lParentIndex, lRow, lRow);
if(pRow == -1)
{
lParentItem->mElement.appendChild(lNewEntry);
lParentItem->mChildren.append(new BBStreamItem(lParentItem, lRow, lNewEntry));
}
else
{
lParentItem->mElement.insertBefore(lNewEntry, lParentItem->mElement.childNodes().item(lRow));
lParentItem->mChildren.insert(pRow, new BBStreamItem(lParentItem, lRow, lNewEntry));
}
endInsertRows();
return index(lRow, 0, lParentIndex);
}
void BBStreamModel::saveDocumentToFile()
{
QFile lXmlFile(mFileName);
if(lXmlFile.open(QIODevice::WriteOnly))
{
QTextStream lTextStream(&lXmlFile);
mDocument.save(lTextStream, 4);
}
}
void BBStreamModel::getInfo(const QModelIndex &pModelIndex, QString &pName, QString &pUrl)
{
if(!pModelIndex.isValid())
return;
BBStreamItem *lItem = static_cast<BBStreamItem *>(pModelIndex.internalPointer());
pName = lItem->mElement.attribute("name");
pUrl = lItem->mElement.attribute("href");
}
Qt::ItemFlags BBStreamModel::flags(const QModelIndex &pIndex) const
{
Qt::ItemFlags lFlags = QAbstractItemModel::flags(pIndex);
if(pIndex.isValid())
{
lFlags |= Qt::ItemIsDragEnabled;
if(pIndex.data(BBIsFolderRole).toBool())
lFlags |= Qt::ItemIsDropEnabled;
return lFlags;
}
else
return Qt::ItemIsDropEnabled | lFlags;
}
Qt::DropActions BBStreamModel::supportedDropActions() const
{
return Qt::MoveAction;
}
bool BBStreamModel::removeRows(int pRow, int pCount, const QModelIndex &pParent)
{
BBStreamItem *lParentItem;
if(!pParent.isValid())
lParentItem = mRoot;
else
lParentItem = static_cast<BBStreamItem *>(pParent.internalPointer());
beginRemoveRows(pParent, pRow, pRow + pCount - 1);
for(int i = pRow; i < pRow + pCount && i < lParentItem->mChildren.count(); ++i)
{
BBStreamItem *lItem = lParentItem->mChildren.takeAt(i);
lParentItem->mElement.removeChild(lItem->mElement);
delete lItem;
}
for(int i = pRow + pCount - 1; i < lParentItem->mChildren.count(); ++i)
lParentItem->mChildren[i]->mRow -= pCount;
endRemoveRows();
saveDocumentToFile();
return true;
}
void BBStreamModel::encodeNode(QDataStream &lDataStream, QModelIndex pIndex) const
{
if(pIndex.data(BBIsFolderRole).toBool())
{
lDataStream << QString::fromAscii("folder");
lDataStream << pIndex.data(Qt::DisplayRole).toString();
qint32 lRowCount = rowCount(pIndex);
lDataStream << lRowCount;
for(qint32 i = 0; i < lRowCount; ++i)
encodeNode(lDataStream, index(i, 0, pIndex));
}
else
{
lDataStream << QString::fromAscii("entry");
lDataStream << pIndex.data(Qt::DisplayRole).toString();
lDataStream << pIndex.data(BBUrlRole).toString();
}
}
bool BBStreamModel::decodeNode(QDataStream &lDataStream, QModelIndex pParent, int pRow)
{
qint32 lRowCount;
QString lType, lUrl, lName;
lDataStream >> lType;
if(lType == "entry")
{
lDataStream >> lName;
lDataStream >> lUrl;
createNewEntry(pParent, lName, lUrl, pRow);
}
else
{
lDataStream >> lName;
QModelIndex i = pParent;
while(i.isValid())
{
if(i.data().toString() == lName)
return false;
i = i.parent();
}
QModelIndex lNewFolderIndex = createNewFolder(pParent, lName, pRow);
lDataStream >> lRowCount;
for(qint32 i = 0; i < lRowCount; ++i)
decodeNode(lDataStream, lNewFolderIndex, i);
}
return true;
}
QMimeData *BBStreamModel::mimeData(const QModelIndexList &pIndexes) const
{
QMimeData *lMimeData = new QMimeData();
QByteArray lByteArray;
QDataStream lDataStream(&lByteArray, QIODevice::WriteOnly);
foreach(QModelIndex lIndex, pIndexes)
encodeNode(lDataStream, lIndex);
lMimeData->setData("application/x-boombox-streamtab", lByteArray);
return lMimeData;
}
bool BBStreamModel::dropMimeData(const QMimeData *pData, Qt::DropAction pAction, int pRow, int pColumn, const QModelIndex &pParent)
{
Q_UNUSED(pAction)
Q_UNUSED(pColumn)
if(!pData->hasFormat("application/x-boombox-streamtab"))
return false;
QByteArray lByteArray = pData->data("application/x-boombox-streamtab");
QDataStream lDataStream(&lByteArray, QIODevice::ReadOnly);
return decodeNode(lDataStream, pParent, pRow);
}
QStringList BBStreamModel::mimeTypes() const
{
QStringList lList;
lList << "application/x-boombox-streamtab";
return lList;
}
BBStreamsTab::BBStreamsTab(int pTabNumber)
:BBPlaylistSystem(pTabNumber)
{
setWindowTitle(i18n("Internet Streams"));
}
void BBStreamsTab::readSession(KConfigGroup &pConfigGroup)
{
Q_UNUSED(pConfigGroup)
mActionCollection = new KActionCollection(this);
KAction *lNewStreamAction = mActionCollection->addAction("new_stream");
lNewStreamAction->setText(i18n("New Stream"));
lNewStreamAction->setIcon(KIcon("document-new"));
connect(lNewStreamAction, SIGNAL(triggered()), this, SLOT(createNewStream()));
KAction *lNewFolderAction = mActionCollection->addAction("new_folder");
lNewFolderAction->setText(i18n("New Folder"));
lNewFolderAction->setIcon(KIcon("folder-new"));
connect(lNewFolderAction, SIGNAL(triggered()), this, SLOT(createNewFolder()));
KAction *lEnqueueAction = new KAction(i18n("Add to Queue"), this);
lEnqueueAction->setShortcut(Qt::CTRL + Qt::Key_E);
lEnqueueAction->setShortcutContext(Qt::WidgetShortcut);
connect(lEnqueueAction, SIGNAL(triggered()), SLOT(enqueueTriggered()));
KAction *lEditAction = new KAction(i18n("Edit"), this);
lEditAction->setShortcut(Qt::Key_F2);
lEditAction->setShortcutContext(Qt::WidgetShortcut);
connect(lEditAction, SIGNAL(triggered()), SLOT(editTriggered()));
KAction *lDeleteAction = new KAction(i18n("Delete"), this);
lDeleteAction->setShortcut(Qt::Key_Delete);
lDeleteAction->setShortcutContext(Qt::WidgetShortcut);
connect(lDeleteAction, SIGNAL(triggered()), SLOT(deleteTriggered()));
mContextMenu = new QMenu(this);
mContextMenu->addAction(lEnqueueAction);
mContextMenu->addAction(lEditAction);
mContextMenu->addAction(lDeleteAction);
mControlToolBar = toolBar("fs_control_toolbar");
mControlToolBar->setWindowTitle(i18n("Playback Control Toolbar"));
mControlToolBar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea);
mControlsContainer = new QWidget(this);
mControlsLayout = new QHBoxLayout(mControlsContainer);
mControlToolBar->addAction(lNewStreamAction);
mControlToolBar->addAction(lNewFolderAction);
mControlToolBar->addWidget(mControlsContainer);
mCenterWidget = new QWidget(this);
mTreeView = new QTreeView(this);
mTreeView->setHeaderHidden(true);
mTreeView->setSelectionMode(QAbstractItemView::SingleSelection);
mTreeView->setDragDropMode(QAbstractItemView::InternalMove);
mTreeView->setAcceptDrops(true);
mTreeView->setDragEnabled(true);
mTreeView->setDropIndicatorShown(true);
QVBoxLayout *lCenterLayout = new QVBoxLayout(mCenterWidget);
mModel = new BBStreamModel(BBSettings::fileNameStreams(), this);
mTreeView->setModel(mModel);
mTreeView->expandAll();
mTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
mTreeView->addAction(lEnqueueAction);
mTreeView->addAction(lEditAction);
mTreeView->addAction(lDeleteAction);
lCenterLayout->addWidget(mTreeView);
setCentralWidget(mCenterWidget);
connect(mTreeView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(showPopupMenu(QPoint)));
connect(mTreeView, SIGNAL(activated(QModelIndex)), this, SLOT(entryClicked(QModelIndex)));
}
void BBStreamsTab::saveSession(KConfigGroup &pConfigGroup)
{
Q_UNUSED(pConfigGroup)
mModel->saveDocumentToFile();
}
void BBStreamsTab::embedControls(QWidget *pControls)
{
pControls->setParent(mControlsContainer);
mControlsLayout->addWidget(pControls);
pControls->show();
}
void BBStreamsTab::buildSearchStack(bool pSearchForward)
{
QModelIndex lCurrent = mModel->mCurrentSong;
if(lCurrent.isValid())
{
if(pSearchForward)
{
do
{
int lStartRow = lCurrent.row() + 1;
lCurrent = lCurrent.parent();
int lMax = mModel->rowCount(lCurrent);
for(int i = lStartRow; i < lMax; ++i)
mSearchStack.append(mModel->index(i, 0, lCurrent));
}while(lCurrent.isValid());
}
else
{
do
{
int lStartRow = lCurrent.row() - 1;
lCurrent = lCurrent.parent();
for(int i = lStartRow; i >= 0; --i)
mSearchStack.append(mModel->index(i, 0, lCurrent));
}while(lCurrent.isValid());
}
}
else
{
if(pSearchForward)
{
int lMax = mModel->rowCount();
for(int i = 0; i < lMax; ++i)
mSearchStack.append(mModel->index(i, 0));
}
else
{
for(int i = mModel->rowCount() - 1; i >= 0; --i)
mSearchStack.append(mModel->index(i, 0));
}
}
}
void BBStreamsTab::findSongInSearchStack(bool pSearchForward, BBSongQueryJob &pJob)
{
while(!mSearchStack.isEmpty())
{
QPersistentModelIndex lCurrent = mSearchStack.takeFirst();
if(!lCurrent.isValid())
continue;
if(!lCurrent.data(BBIsFolderRole).toBool())
{
pJob.mSong = QVariant::fromValue(lCurrent);
emit songQueryReady(pJob);
mSearchStack.clear();
return;
}
else
{
if(pSearchForward)
{
for(int i = mModel->rowCount(lCurrent) - 1; i >= 0; --i)
mSearchStack.prepend(mModel->index(i, 0, lCurrent));
}
else
{
for(int i = 0; i < mModel->rowCount(lCurrent); ++i)
mSearchStack.prepend(mModel->index(i, 0, lCurrent));
}
}
}
}
void BBStreamsTab::queryNextSong(BBSongQueryJob &pJob)
{
mSearchStack.clear();
buildSearchStack(true);
findSongInSearchStack(true, pJob);
}
void BBStreamsTab::queryPreviousSong(BBSongQueryJob &pJob)
{
mSearchStack.clear();
buildSearchStack(false);
findSongInSearchStack(false, pJob);
}
QVariant BBStreamsTab::currentSong()
{
return QVariant::fromValue(mModel->mCurrentSong);
}
void BBStreamsTab::setCurrentSong(const QVariant &pSong)
{
mTreeView->update(mModel->mCurrentSong);
mTreeView->update(mModel->mCurrentSong.parent());
mModel->mCurrentSong = pSong.value<QPersistentModelIndex>();
mTreeView->scrollTo(mModel->mCurrentSong);
mTreeView->update(mModel->mCurrentSong);
mTreeView->update(mModel->mCurrentSong.parent());
}
QString BBStreamsTab::displayString(const QVariant &pSong)
{
return pSong.value<QPersistentModelIndex>().data(Qt::DisplayRole).toString();
}
KUrl BBStreamsTab::songUrl(const QVariant &pSong)
{
QPersistentModelIndex lSong = pSong.value<QPersistentModelIndex>();
if(!lSong.isValid())
return KUrl();
else
return KUrl(lSong.data(BBUrlRole).toString());
}
void BBStreamsTab::createNewStream()
{
QString lName, lUrl;
BBStreamEditForm *lDialog = new BBStreamEditForm(lName, lUrl, this);
if(QDialog::Accepted == lDialog->exec())
{
mModel->createNewEntry(mTreeView->selectionModel()->currentIndex(), lDialog->mName, lDialog->mUrl);
mModel->saveDocumentToFile();
}
delete lDialog;
}
void BBStreamsTab::createNewFolder()
{
QString lNewName = KInputDialog::getText(i18nc("dialog caption", "New Folder"),
i18n("Please provide a name for the new folder:"),
i18nc("default text", "New Folder"));
if(lNewName.isNull())
return;
mModel->createNewFolder(mTreeView->selectionModel()->currentIndex(), lNewName);
mModel->saveDocumentToFile();
}
void BBStreamsTab::showPopupMenu(const QPoint &pPoint)
{
QModelIndex lIndex = mTreeView->indexAt(pPoint);
if(lIndex.isValid())
mContextMenu->popup(mTreeView->mapToGlobal(pPoint));
}
void BBStreamsTab::editTriggered()
{
QString lName, lUrl;
mModel->getInfo(mTreeView->selectionModel()->currentIndex(), lName, lUrl);
if(mTreeView->selectionModel()->currentIndex().data(BBIsFolderRole).toBool())
{
QString lNewName = KInputDialog::getText(i18nc("dialog caption", "New Folder"),
i18n("Please provide a new name for the folder:"), lName);
if(lNewName.isNull())
return;
mModel->editEntry(mTreeView->selectionModel()->currentIndex(), lNewName, lUrl);
mModel->saveDocumentToFile();
}
else
{
BBStreamEditForm *lDialog = new BBStreamEditForm(lName, lUrl, this);
if(QDialog::Accepted == lDialog->exec())
{
mModel->editEntry(mTreeView->selectionModel()->currentIndex(), lDialog->mName, lDialog->mUrl);
mModel->saveDocumentToFile();
}
delete lDialog;
}
}
void BBStreamsTab::deleteTriggered()
{
mModel->deleteEntry(mTreeView->selectionModel()->currentIndex());
mModel->saveDocumentToFile();
}
void BBStreamsTab::entryClicked(QModelIndex pIndex)
{
if(pIndex.isValid())
{
QPersistentModelIndex lIndex = pIndex;
gMainWindow->setCurrentPlaylistSystem(tabNumber());
gMainWindow->setCurrentSong(QVariant::fromValue(lIndex));
}
}
void BBStreamsTab::enqueueTriggered()
{
QModelIndex lIndex = mTreeView->selectionModel()->currentIndex();
if(!lIndex.data(BBIsFolderRole).toBool())
{
gMainWindow->addToPlayQueue(QVariant::fromValue(QPersistentModelIndex(lIndex)),
lIndex.data().toString(), tabNumber());
}
}
void BBStreamsTab::addManualUrl(const KUrl &pUrl, const QString &pTitle)
{ //TODO: search for URL match in existing entries, don't add if already there.
QModelIndex lIndex = mModel->createNewEntry(QModelIndex(), pTitle, pUrl.url());
gMainWindow->addToPlayQueue(QVariant::fromValue(QPersistentModelIndex(lIndex)), pTitle, tabNumber());
}

113
boombox/src/bbstreamstab.h Normal file
View file

@ -0,0 +1,113 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef BBSTREAMSTAB_H
#define BBSTREAMSTAB_H
#include "bbplaylistsystem.h"
#include <QAbstractItemModel>
#include <QDomDocument>
class QHBoxLayout;
class QTreeView;
class KActionCollection;
class KToolBar;
class BBStreamItem;
class BBStreamModel : public QAbstractItemModel
{
public:
explicit BBStreamModel(const QString &pFileName, QObject *pParent = 0);
virtual ~BBStreamModel();
virtual QModelIndex index(int pRow, int pColumn, const QModelIndex &pParent = QModelIndex()) const;
virtual QModelIndex parent(const QModelIndex &pChild) const;
virtual int rowCount(const QModelIndex &pParent = QModelIndex()) const;
virtual int columnCount(const QModelIndex &pParent = QModelIndex()) const;
virtual QVariant data(const QModelIndex &pIndex, int pRole = Qt::DisplayRole) const;
virtual Qt::ItemFlags flags(const QModelIndex &pIndex) const;
virtual Qt::DropActions supportedDropActions() const;
virtual bool removeRows(int pRow, int pCount, const QModelIndex &pParent);
virtual QMimeData *mimeData(const QModelIndexList &pIndexes) const;
virtual bool dropMimeData(const QMimeData *pData, Qt::DropAction pAction, int pRow, int pColumn, const QModelIndex &pParent);
virtual QStringList mimeTypes() const;
QPersistentModelIndex mCurrentSong;
QModelIndex createNewEntry(const QModelIndex &pParent, const QString &pName, const QString &pUrl, int pRow = -1);
void editEntry(const QModelIndex &pEntry, const QString &pName, const QString &pUrl);
void deleteEntry(const QModelIndex &pEntry);
QModelIndex createNewFolder(const QModelIndex &pParent, const QString &pName, int pRow = -1);
void saveDocumentToFile();
void getInfo(const QModelIndex &pModelIndex, QString &pName, QString &pUrl);
protected:
void encodeNode(QDataStream &lDataStream, QModelIndex pIndex) const;
bool decodeNode(QDataStream &lDataStream, QModelIndex pParent, int pRow);
QDomDocument mDocument;
QString mFileName;
BBStreamItem *mRoot;
};
class BBStreamsTab : public BBPlaylistSystem
{
Q_OBJECT
public:
explicit BBStreamsTab(int pTabNumber);
virtual void queryNextSong(BBSongQueryJob &pJob);
virtual void queryPreviousSong(BBSongQueryJob &pJob);
virtual QVariant currentSong();
virtual void setCurrentSong(const QVariant &pSong);
virtual QString displayString(const QVariant &pSong);
virtual KUrl songUrl(const QVariant &pSong);
void addManualUrl(const KUrl &pUrl, const QString &pTitle);
virtual void readSession(KConfigGroup &pConfigGroup);
virtual void saveSession(KConfigGroup &pConfigGroup);
virtual void embedControls(QWidget *pControls);
protected slots:
void createNewStream();
void createNewFolder();
void showPopupMenu(const QPoint &pPoint);
void editTriggered();
void deleteTriggered();
void entryClicked(QModelIndex pIndex);
void enqueueTriggered();
protected:
void buildSearchStack(bool pSearchForward);
void findSongInSearchStack(bool pSearchForward, BBSongQueryJob &pJob);
QList<QModelIndex> mSearchStack;
KToolBar *mControlToolBar;
QWidget *mControlsContainer;
QHBoxLayout *mControlsLayout;
QWidget *mCenterWidget;
QTreeView *mTreeView;
BBStreamModel *mModel;
KActionCollection *mActionCollection;
QMenu *mContextMenu;
};
#endif // BBSTREAMSTAB_H

View file

@ -0,0 +1,15 @@
[Desktop Entry]
Version=1.0
Type=Application
Name=BoomBox
Name[x-test]=xxBoomBoxxx
Exec=boombox %i -graphicssystem raster -caption "%c" %U
Icon=boombox
Categories=KDE;Qt;AudioVideo;Audio;Player;
GenericName=Music player
GenericName[sv]=Musikspelare
GenericName[en]=Music Player
MimeType=application/x-ogg;audio/basic;audio/vnd.rn-realaudio;audio/x-aiff;audio/x-flac;audio/x-matroska;audio/x-mp3;audio/mpeg;audio/mp4;audio/ogg;audio/x-flac+ogg;audio/x-vorbis+ogg;audio/x-mpegurl;audio/x-ms-wma;audio/x-pn-realaudio;audio/x-scpls;audio/x-wav;
DocPath=boombox/index.html
Terminal=false
X-KDE-StartupNotify=true

31
boombox/src/boombox.kcfg Normal file
View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<kcfgfile name="boomboxrc"/>
<include>KStandardDirs</include>
<include>KGlobalSettings</include>
<include>KGlobal</include>
<group name="musicfiles">
<entry name="MusicDirectories" type="PathList">
<label>Directories to scan for music files</label>
<default code="true">QStringList(KGlobalSettings::musicPath())</default>
</entry>
<entry name="FileNameDB" type="Path">
<label>Location of the database file</label>
<default code="true">KGlobal::dirs()->saveLocation("appdata") + "music.db"</default>
</entry>
<entry name="FileNameStreams" type="Path">
<label>Location of the internet streams database file</label>
<default code="true">KGlobal::dirs()->saveLocation("appdata") + "streams.xml"</default>
</entry>
<entry name="DatabaseUpdateTime" type="DateTime">
<label>The last time the database was updated</label>
</entry>
</group>
</kcfg>

179
boombox/src/dbqueryjob.cpp Normal file
View file

@ -0,0 +1,179 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "dbqueryjob.h"
#include "bbdatabase.h"
#include "bbsettings.h"
#include <KLocale>
DBPool::DBPool(int pNumConnections)
: mNumConnections(pNumConnections)
{
for(int i = 0; i < mNumConnections; ++i)
mAvailable.push(new BBDatabase());
}
DBPool::~DBPool()
{
foreach(BBDatabase *i, mAvailable)
{
delete i;
}
}
bool DBPool::canRun(ThreadWeaver::Job *pJob)
{
if(mAvailable.isEmpty())
return false;
else
{
DBQueryJob * lJob = qobject_cast<DBQueryJob *>(pJob);
if(lJob == NULL)
return false;
if(lJob->mDatabase != NULL)
return true;
lJob->mDatabase = mAvailable.pop();
if(!lJob->mDatabase->isConnected())
{
if(!lJob->mDatabase->connect(BBSettings::self()->fileNameDB()))
return false;
}
return true;
}
}
void DBPool::free(ThreadWeaver::Job *pJob)
{
DBQueryJob * lJob = qobject_cast<DBQueryJob *>(pJob);
if(lJob == NULL)
return;
if(lJob->mDatabase != NULL)
mAvailable.push(lJob->mDatabase);
lJob->mDatabase = NULL;
}
void FilterQueryJob::run()
{
BBResultTable lDBResult;
QSet<BBStringListItem> lResult;
if(mDatabase->getTable(QString("SELECT DISTINCT %1 FROM songs JOIN albums USING (album_ID) %2 ORDER BY %1 ASC"
).arg(mCategory, mCondition.isEmpty() ? mCondition: QString("WHERE %1").arg(mCondition)), lDBResult))
{
for(int i = 1; i <= lDBResult.mNumRows; ++i)//start with 1 since first row is column names
{
if(mShouldAbort)
{
BBDatabase::freeTable(lDBResult);
return;
}
lResult.insert(BBStringListItem(QString::fromUtf8(lDBResult.at(i, 0)), i-1));
}
BBDatabase::freeTable(lDBResult);
}
if(!mShouldAbort)
emit resultReady(lResult);
}
void SongQueryJob::getSongs(bool pVA)
{
BBResultTable lDBResult;
QString lQuery;
if(pVA)
{
lQuery = QString("SELECT title, artist, length, file_name, file_ID, album_ID, album, "
"path, cover_art_path FROM songs JOIN albums USING (album_ID) WHERE is_VA != 0 %1 "
"ORDER BY album, path, track, file_name"
).arg(mCondition.isEmpty() ? mCondition : QString("AND %1").arg(mCondition));
}
else
{
lQuery = QString("SELECT title, artist, length, file_name, file_ID, album_ID, album, "
"path, cover_art_path FROM songs JOIN albums USING (album_ID) WHERE is_VA = 0 %1 "
"ORDER BY artist, album, path, track, file_name"
).arg(mCondition.isEmpty() ? mCondition : QString("AND %1").arg(mCondition));
}
if(mDatabase->getTable(lQuery, lDBResult))
{
int lCurrentAlbumId = -1; //for use in first run of loop
int lSongPosition = 0;
QString lTemp;
BBAlbumListItem lAlbum;
BBSongListItem lSong;
lAlbum.mPosition = -1; //for use in first run of loop
for(int i = 1; i <= lDBResult.mNumRows; ++i)//start with 1 since first row is column names
{
if(mShouldAbort)
{
BBDatabase::freeTable(lDBResult);
return;
}
lSong.mData.mTitle = QString::fromUtf8(lDBResult.at(i,0));
if(pVA)
lSong.mData.mArtist = QString::fromUtf8(lDBResult.at(i,1));
lSong.mData.mLength = QString::fromUtf8(lDBResult.at(i,2));
lSong.mData.mFileName = QString::fromUtf8(lDBResult.at(i,3));
lTemp = QString::fromUtf8(lDBResult.at(i,4));
lSong.mData.mFileID = lTemp.toInt();
lTemp = QString::fromUtf8(lDBResult.at(i,5));
lSong.mData.mAlbumID = lTemp.toInt();
if(lCurrentAlbumId != lSong.mData.mAlbumID)
{
if(lAlbum.mPosition >= 0)
{
mAlbumSet.insert(lAlbum);
lSongPosition = 0;
}
lAlbum.mData.mAlbum = QString::fromUtf8(lDBResult.at(i,6));
lAlbum.mData.mFolderPath = QString::fromUtf8(lDBResult.at(i,7));
lAlbum.mData.mCoverArtPath= QString::fromUtf8(lDBResult.at(i,8));
lAlbum.mData.mIsVA = pVA;
lAlbum.mData.mArtist = pVA ? i18n("Various Artists") : QString::fromUtf8(lDBResult.at(i,1));
lAlbum.mPosition = mAlbumSet.count();
lAlbum.mData.mAlbumID = lSong.mData.mAlbumID;
lCurrentAlbumId = lSong.mData.mAlbumID;
lAlbum.mData.mManuallyExpanded = false; //initially all albums are not expanded
}
lSong.mIndex.mSongPos = lSongPosition++;
lSong.mIndex.mAlbumPos = mAlbumSet.count();
lSong.mPosition = mSongSet.count();
mSongSet.insert(lSong);
}
if(lAlbum.mPosition >= 0)
mAlbumSet.insert(lAlbum);
BBDatabase::freeTable(lDBResult);
}
}
void SongQueryJob::run()
{
getSongs(false);
if(mShouldAbort)
return;
getSongs(true);
if(!mShouldAbort)
emit resultReady(mSongSet, mAlbumSet);
}
#include "dbqueryjob.moc"

114
boombox/src/dbqueryjob.h Normal file
View file

@ -0,0 +1,114 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef DBQUERYJOB_H
#define DBQUERYJOB_H
#include <threadweaver/ThreadWeaver.h>
#include <threadweaver/QueuePolicy.h>
#include <threadweaver/Job.h>
#include <QStringList>
#include <QStack>
#include <QSet>
#include "bblistitem.h"
class BBDatabase;
class DBPool: public ThreadWeaver::QueuePolicy
{
public:
DBPool(int pNumConnections);
virtual ~DBPool();
virtual bool canRun(ThreadWeaver::Job *pJob);
virtual void free(ThreadWeaver::Job *pJob);
virtual void release(ThreadWeaver::Job *pJob)
{
free(pJob);
}
virtual void destructed(ThreadWeaver::Job *pJob)
{
free(pJob);
}
protected:
int mNumConnections;
QStack<BBDatabase *> mAvailable;
};
class DBQueryJob: public ThreadWeaver::Job
{
Q_OBJECT
public:
DBQueryJob()
: mDatabase(NULL)
{}
protected:
BBDatabase *mDatabase;
friend class DBPool;
};
class FilterQueryJob: public DBQueryJob
{
Q_OBJECT
public:
FilterQueryJob(const QString &pCategory, const QString &pCondition)
: mCategory(pCategory), mCondition(pCondition), mShouldAbort(false)
{}
virtual void requestAbort() {mShouldAbort = true;}
signals:
void resultReady(QSet<BBStringListItem>);
protected:
virtual void run();
QString mCategory, mCondition;
bool mShouldAbort;
};
class SongQueryJob: public DBQueryJob
{
Q_OBJECT
public:
SongQueryJob(const QString &pCondition)
: mCondition(pCondition), mShouldAbort(false)
{}
virtual void requestAbort() {mShouldAbort = true;}
signals:
void resultReady(QSet<BBSongListItem>, QSet<BBAlbumListItem>);
protected:
virtual void run();
void getSongs(bool pVA);
QString mCategory, mCondition;
bool mShouldAbort;
QSet<BBSongListItem> mSongSet;
QSet<BBAlbumListItem> mAlbumSet;
};
#endif

201
boombox/src/dbupdatejob.cpp Normal file
View file

@ -0,0 +1,201 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "dbupdatejob.h"
#include "bbdatabase.h"
#include "bbmetadata.h"
#include "bbsettings.h"
#include <QMap>
#include <QFileInfo>
#include <QDir>
#include <KLocale>
#include <sqlite3.h>
DBUpdateJob::DBUpdateJob()
{
mFileTypes << "*.mp3" << "*.MP3" <<"*.ogg" <<"*.OGG" <<"*.flac" <<"*.FLAC" <<"*.mpc" << "*.MPC";
setCapabilities(Killable | Suspendable);
}
void DBUpdateJob::aboutToDie()
{
mDatabase.executeStatement("ROLLBACK");
}
void DBUpdateJob::doWork()
{
QMap<QString, int> lDatabaseSet, lFileSystemSet;
emit infoMessage(this, i18n("Reading list of files from database"));
mDatabase.connect(BBSettings::fileNameDB());
BBResultTable lResult;
mDatabase.getTable("SELECT path || '/' || file_name, file_ID FROM songs JOIN albums USING (album_ID)", lResult);
for(int i = 1; i <= lResult.mNumRows; ++i)
{
if(checkForDeathOrSuspend())
return;
lDatabaseSet.insert(QString::fromUtf8(lResult.at(i,0)), QString::fromUtf8(lResult.at(i,1)).toInt());
}
mDatabase.freeTable(lResult);
emit infoMessage(this, i18n("Scanning directories for music files"));
QStringList lPathList = BBSettings::musicDirectories();
QString lPath;
foreach(lPath, lPathList)
{
if(!addPathToSet(lPath, lFileSystemSet))
return;
}
mDatabase.executeStatement("BEGIN");
QMapIterator<QString, int> i(lDatabaseSet);
while(i.hasNext())
{
if(checkForDeathOrSuspend())
return;
i.next();
if(!lFileSystemSet.contains(i.key()) && !i.key().contains("://"))
{
emit description(this, i18n("Removing old entries"), qMakePair(i18n("Path"), i.key()));
mDatabase.executeStatement(QString("DELETE FROM songs WHERE file_ID = %1").arg(i.value()));
}
}
mDatabase.executeStatement("COMMIT");
mDatabase.executeStatement("BEGIN");
setTotalAmount(Files, lFileSystemSet.count());
BBMetaData lMetaData;
int lInsertCount = 0, lFileCount=0;
i = lFileSystemSet;
while(i.hasNext())
{
if(checkForDeathOrSuspend())
return;
i.next();
QFileInfo lFileInfo(i.key());
bool lSongInDatabase = lDatabaseSet.contains(i.key());
if(lFileInfo.isFile() &&
(!lSongInDatabase || lFileInfo.lastModified() > BBSettings::databaseUpdateTime()))
{
if(lSongInDatabase)
mDatabase.executeStatement(QString("DELETE FROM songs WHERE file_ID = %1").arg(lDatabaseSet.value(i.key())));
lMetaData.GetInfoFrom(lFileInfo.absolutePath(), lFileInfo.fileName());
emit description(this, i18n("Adding new entries"),
qMakePair(i18n("Path"), lFileInfo.absoluteFilePath()),
qMakePair(i18n("Info"), QString("%1 - %2").arg(lMetaData.mArtist, lMetaData.mTitle)));
addSongToDatabase(lMetaData);
lInsertCount++;
}
if(lInsertCount > 99) //commit to db every hundred inserts.
{
mDatabase.executeStatement("COMMIT");
mDatabase.executeStatement("BEGIN");
lInsertCount = 0;
}
lFileCount++;
setProcessedAmount(Files, lFileCount);
setPercent((ulong)(100*processedAmount(Files))/totalAmount(Files)); //BUG in KJob??
}
mDatabase.executeStatement("COMMIT");
mDatabase.executeStatement("DELETE FROM dead_album_view");
mDatabase.executeStatement("UPDATE albums SET is_VA = 1 WHERE album_ID IN "
"(SELECT album_ID FROM songs JOIN albums USING (album_ID) "
"GROUP BY album_ID HAVING count(DISTINCT artist) > 1)");
mDatabase.executeStatement("UPDATE albums SET is_VA = 0 WHERE album_ID IN "
"(SELECT album_ID FROM songs JOIN albums USING (album_ID) "
"GROUP BY album_ID HAVING count(DISTINCT artist) = 1)");
emitResult();
}
void DBUpdateJob::addSongToDatabase(BBMetaData &pMetaData)
{
int lAlbumID;
BBResultTable lResult;
mDatabase.getTable(QString("SELECT album_ID FROM albums WHERE path = '%1' AND album = '%2'"
).arg(BBDatabase::prepareString(pMetaData.mPath),
BBDatabase::prepareString(pMetaData.mAlbum)), lResult);
if(lResult.mNumRows == 0)
{
mDatabase.executeStatement(QString("INSERT INTO albums (path, album, is_VA, cover_art_path) VALUES ('%1','%2','%3','%4')"
).arg(BBDatabase::prepareString(pMetaData.mPath),
BBDatabase::prepareString(pMetaData.mAlbum),
QString::number(0), QString()));
lAlbumID = sqlite3_last_insert_rowid(mDatabase.getDatabase());
}
else
{
lAlbumID = QString::fromUtf8(lResult.at(1, 0)).toInt();
}
mDatabase.freeTable(lResult);
mDatabase.executeStatement(QString("INSERT INTO songs (album_ID, file_name, artist, title, track, year, genre, "
"comment, length) VALUES ('%1', '%2', '%3', '%4', '%5', '%6', '%7', '%8', '%9')"
).arg(lAlbumID).arg(BBDatabase::prepareString(pMetaData.mFileName),
BBDatabase::prepareString(pMetaData.mArtist),
BBDatabase::prepareString(pMetaData.mTitle),
BBDatabase::prepareString(pMetaData.mTrack),
BBDatabase::prepareString(pMetaData.mYear),
BBDatabase::prepareString(pMetaData.mGenre),
BBDatabase::prepareString(pMetaData.mComment),
BBDatabase::prepareString(pMetaData.mLength)));
pMetaData.mFileID = sqlite3_last_insert_rowid(mDatabase.getDatabase());
}
bool DBUpdateJob::addPathToSet(const QString &pPath, QMap<QString, int> &pSet)
{
QDir lDir(pPath);
if(!lDir.isReadable())
return true;
if(checkForDeathOrSuspend())
return false;
QFileInfoList lInfoList = lDir.entryInfoList(QDir::Dirs | QDir::Readable | QDir::NoDotAndDotDot);
QFileInfo lFileInfo;
foreach(lFileInfo, lInfoList)
{
if(!addPathToSet(lFileInfo.absoluteFilePath(), pSet))
return false;
}
lInfoList = lDir.entryInfoList(mFileTypes, QDir::Files | QDir::Readable);
foreach(lFileInfo, lInfoList)
{
if(checkForDeathOrSuspend())
return false;
pSet.insert(lFileInfo.absoluteFilePath(), 0);
}
return true;
}
#include "dbupdatejob.moc"

48
boombox/src/dbupdatejob.h Normal file
View file

@ -0,0 +1,48 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef DBUPDATEJOB_H
#define DBUPDATEJOB_H
#include "threadjob.h"
#include "bbdatabase.h"
#include <QStringList>
class DBUpdateJob: public ThreadJob
{
Q_OBJECT
public:
DBUpdateJob();
public slots:
void doWork();
protected:
void aboutToDie();
private:
bool addPathToSet(const QString &pPath, QMap<QString,int> &pSet);
void addSongToDatabase(BBMetaData &pMetaData);
BBDatabase mDatabase;
QStringList mFileTypes;
};
#endif

View file

@ -0,0 +1,43 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name='org.mpris.MediaPlayer2.Player'>
<method name='Next'/>
<method name='Previous'/>
<method name='Pause'/>
<method name='PlayPause'/>
<method name='Stop'/>
<method name='Play'/>
<method name='Seek'>
<arg direction='in' name='Offset' type='x'/>
</method>
<method name='SetPosition'>
<arg direction='in' name='TrackId' type='o'/>
<arg direction='in' name='Position' type='x'/>
</method>
<method name='OpenUri'>
<arg direction='in' name='Uri' type='s'/>
</method>
<signal name='Seeked'>
<arg name='Position' type='x'/>
</signal>
<property name='PlaybackStatus' type='s' access='read'/>
<property name='LoopStatus' type='s' access='readwrite'/>
<property name='Rate' type='d' access='readwrite'/>
<property name='Shuffle' type='b' access='readwrite'/>
<property name='Metadata' type='a{sv}' access='read'>
<annotation name="com.trolltech.QtDBus.QtTypeName" value="QVariantMap"/>
</property>
<property name='Volume' type='d' access='readwrite'/>
<property name='Position' type='x' access='read'/>
<property name='MinimumRate' type='d' access='read'/>
<property name='MaximumRate' type='d' access='read'/>
<property name='CanGoNext' type='b' access='read'/>
<property name='CanGoPrevious' type='b' access='read'/>
<property name='CanPlay' type='b' access='read'/>
<property name='CanPause' type='b' access='read'/>
<property name='CanSeek' type='b' access='read'/>
<property name='CanControl' type='b' access='read'/>
</interface>
</node>

View file

@ -0,0 +1,45 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name='org.mpris.MediaPlayer2.TrackList'>
<method name='GetTracksMetadata'>
<arg direction='in' name='TrackIds' type='ao'/>
<arg direction='out' name='Metadata' type='aa{sv}'>
<annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="TrackMetadata" />
</arg>
</method>
<method name='AddTrack'>
<arg direction='in' name='Uri' type='s'/>
<arg direction='in' name='AfterTrack' type='o'/>
<arg direction='in' name='SetAsCurrent' type='b'/>
</method>
<method name='RemoveTrack'>
<arg direction='in' name='TrackId' type='o'/>
</method>
<method name='GoTo'>
<arg direction='in' name='TrackId' type='o'/>
</method>
<signal name='TrackListReplaced'>
<arg name='Tracks' type='ao'/>
<arg name='CurrentTrack' type='o'/>
</signal>
<signal name='TrackAdded'>
<arg name='Metadata' type='a{sv}'>
<annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="TrackMetadata"/>
</arg>
<arg name='AfterTrack' type='o'/>
</signal>
<signal name='TrackRemoved'>
<arg name='TrackId' type='o'/>
</signal>
<signal name='TrackMetadataChanged'>
<arg name='TrackId' type='o'/>
<arg name='Metadata' type='a{sv}'>
<annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="TrackMetadata"/>
</arg>
</signal>
<property name='Tracks' type='ao' access='read'/>
<property name='CanEditTracks' type='b' access='read'/>
</interface>
</node>

View file

@ -0,0 +1,16 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name='org.mpris.MediaPlayer2'>
<method name='Raise'/>
<method name='Quit'/>
<property name='CanQuit' type='b' access='read'/>
<property name='CanRaise' type='b' access='read'/>
<property name='HasTrackList' type='b' access='read'/>
<property name='Identity' type='s' access='read'/>
<property name='DesktopEntry' type='s' access='read'/>
<property name='SupportedUriSchemes' type='as' access='read'/>
<property name='SupportedMimeTypes' type='as' access='read'/>
</interface>
</node>

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DirectoriesPage</class>
<widget class="QWidget" name="DirectoriesPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>412</width>
<height>425</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="KEditListWidget" name="kcfg_MusicDirectories">
<property name="buttons">
<set>KEditListWidget::Add|KEditListWidget::Remove</set>
</property>
</widget>
</item>
<item>
<widget class="KLineEdit" name="kcfg_FileNameDB"/>
</item>
</layout>
</item>
</layout>
</widget>
<layoutdefault spacing="4" margin="4"/>
<customwidgets>
<customwidget>
<class>KEditListWidget</class>
<extends>QWidget</extends>
<header>keditlistwidget.h</header>
</customwidget>
<customwidget>
<class>KLineEdit</class>
<extends>QLineEdit</extends>
<header>klineedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

88
boombox/src/main.cpp Normal file
View file

@ -0,0 +1,88 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "bbmainwindow.h"
#include "mpris2playerclient.h"
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusReply>
#include <KStartupInfo>
#include <KApplication>
#include <KAboutData>
#include <KCmdLineArgs>
#include <KLocale>
static const char description[] = I18N_NOOP("Music player with dynamic playlists.");
static const char version[] = "0.4";
BBMainWindow *gMainWindow;
extern "C" int main(int argc, char **argv)
{
KAboutData lAbout("boombox", 0, ki18n("BoomBox"), version, ki18n(description),
KAboutData::License_GPL, ki18n("(C) 2009 Simon Persson"),
KLocalizedString(), 0, "simonop@spray.se");
lAbout.addAuthor( ki18n("Simon Persson"), KLocalizedString(), "simonop@spray.se" );
KCmdLineArgs::init(argc, argv, &lAbout);
KCmdLineOptions lOptions;
lOptions.add("+[URL]", ki18n("File or URL to add to playlist"));
KCmdLineArgs::addCmdLineOptions(lOptions);
KApplication lApp;
QDBusConnection lConnection = QDBusConnection::sessionBus();
QDBusConnectionInterface *lSessionInterface = lConnection.interface();
QDBusReply<bool> lIsRegistered = lSessionInterface->isServiceRegistered(QLatin1String("org.mpris.MediaPlayer2.BoomBox"));
if(!lIsRegistered.value())
{
lApp.setAttribute(Qt::AA_DontUseNativeMenuBar);
gMainWindow = new BBMainWindow(&lAbout);
gMainWindow->readSession();
gMainWindow->show();
}
QDBusPendingReply<> lReply;
KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
if (args->count() > 0)
{
org::mpris::MediaPlayer2::Player *lInterface;
lInterface = new org::mpris::MediaPlayer2::Player(QLatin1String("org.mpris.MediaPlayer2.BoomBox"),
QLatin1String("/org/mpris/MediaPlayer2"), lConnection);
for(int i = 0; i < args->count(); ++i)
lReply = lInterface->OpenUri(args->arg(i));
}
if(lIsRegistered.value())
{
KStartupInfo::appStarted(); //make startup notification go away.
if(args->count() > 0)
lReply.waitForFinished();
return 0; //one instance already running in this session.
}
else
{
if(args->count() > 0)
gMainWindow->togglePlayback();
return lApp.exec();
}
}

View file

@ -0,0 +1,465 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "mpris2player.h"
#include "bbmainwindow.h"
#include "bbstreamstab.h"
#include "bbfilesystemtab.h"
#include <phonon/audiooutput.h>
#include <phonon/seekslider.h>
#include <phonon/mediaobject.h>
#include <phonon/volumeslider.h>
#include <KUrl>
#include <KMessageBox>
#include <KMimeType>
#include <KIO/NetAccess>
#include <QFile>
Mpris2Player::Mpris2Player(QObject *pParent)
: QObject(pParent)
{
}
bool Mpris2Player::canQuit() const
{
return true;
}
bool Mpris2Player::canRaise() const
{
return true;
}
QString Mpris2Player::desktopEntry() const
{
return QString::fromAscii("boombox");
}
bool Mpris2Player::hasTrackList() const
{
return false;
}
QString Mpris2Player::identity() const
{
return QString::fromAscii("BoomBox");
}
QStringList Mpris2Player::supportedMimeTypes() const
{
QString lList = QString::fromAscii("application/x-ogg;audio/basic;audio/vnd.rn-realaudio;"
"audio/x-aiff;audio/x-flac;audio/x-matroska;audio/x-mp3;"
"audio/mpeg;audio/ogg;audio/x-flac+ogg;audio/x-vorbis+ogg;"
"audio/x-mpegurl;audio/x-ms-wma;audio/x-pn-realaudio;"
"audio/x-scpls;audio/x-wav");
return lList.split(';');
}
QStringList Mpris2Player::supportedUriSchemes() const
{
QStringList lList;
lList << "http://" << "file://";
return lList;
}
void Mpris2Player::Quit()
{
gMainWindow->close();
}
void Mpris2Player::Raise()
{
gMainWindow->activateWindow();
gMainWindow->raise();
}
bool Mpris2Player::canControl() const
{
return true;
}
bool Mpris2Player::canGoNext() const
{
return true;
}
bool Mpris2Player::canGoPrevious() const
{
return true;
}
bool Mpris2Player::canPause() const
{
return true;
}
bool Mpris2Player::canPlay() const
{
return true;
}
bool Mpris2Player::canSeek() const
{
return gMainWindow->mMediaObject->isSeekable();
}
QString Mpris2Player::loopStatus() const
{
return QString::fromAscii("Playlist");
}
void Mpris2Player::setLoopStatus(const QString &value)
{
Q_UNUSED(value)
}
double Mpris2Player::maximumRate() const
{
return 1.0;
}
QVariantMap Mpris2Player::metadata() const
{
QMultiMap<QString, QString> lMap = gMainWindow->mMediaObject->metaData();
QVariantMap lMap2;
QString lUrl = gMainWindow->mMediaObject->currentSource().url().toString();
if(lUrl.isEmpty()) {
lUrl = "org/mpris/MediaPlayer2/TrackList/NoTrack";
} else {
lUrl.prepend("boombox/");
}
lMap2.insert("mpris:trackid", lUrl);
lMap2.insert("mpris:length", gMainWindow->mMediaObject->totalTime() * 1000);
lMap2.insert("xesam:artist", QStringList(lMap.value("ARTIST")));
lMap2.insert("xesam:album", lMap.value("ALBUM"));
lMap2.insert("xesam:title", lMap.value("TITLE"));
lMap2.insert("xesam:genre", QStringList(lMap.value("GENRE")));
return lMap2;
}
double Mpris2Player::minimumRate() const
{
return 1.0;
}
QString Mpris2Player::playbackStatus() const
{
Phonon::State lState = gMainWindow->mMediaObject->state();
if(lState == Phonon::PlayingState || lState == Phonon::BufferingState)
return "Playing";
else if(lState == Phonon::PausedState)
return "Paused";
else
return "Stopped";
}
qlonglong Mpris2Player::position() const
{
return gMainWindow->mMediaObject->currentTime() * 1000; // return position in microseconds
}
double Mpris2Player::rate() const
{
return 1.0;
}
void Mpris2Player::setRate(double value)
{
Q_UNUSED(value)
}
bool Mpris2Player::shuffle() const
{
return gMainWindow->shuffleActive();
}
void Mpris2Player::setShuffle(bool value)
{
gMainWindow->setShuffle(value);
}
double Mpris2Player::volume() const
{
return gMainWindow->mAudioOutput->volume();
}
void Mpris2Player::setVolume(double value)
{
gMainWindow->mAudioOutput->setVolume(value);
}
void Mpris2Player::Next()
{
gMainWindow->jumpToNextSong();
}
void Mpris2Player::OpenUri(const QString &Uri)
{
KUrl lUrl(Uri);
KMimeType::Ptr lMimeType = KMimeType::findByUrl(lUrl);
if(lMimeType->is("application/octet-stream"))
{
lMimeType = KMimeType::mimeType(KIO::NetAccess::mimetype(lUrl, gMainWindow));
}
if(lMimeType.isNull())
return;
if(lMimeType->is("audio/x-mpegurl") || lMimeType->is("audio/x-scpls"))
{
QString lTmpFile;
if(KIO::NetAccess::download(lUrl, lTmpFile, gMainWindow))
{
if(lMimeType->is("audio/x-mpegurl"))
openM3U(lTmpFile);
else if(lMimeType->is("audio/x-scpls"))
openPLS(lTmpFile);
KIO::NetAccess::removeTempFile(lTmpFile);
}
else
KMessageBox::error(gMainWindow, KIO::NetAccess::lastErrorString());
return;
}
if(lUrl.protocol() == "http")
gMainWindow->mStreamsTab->addManualUrl(lUrl, lUrl.prettyUrl());
else
gMainWindow->mFileSystemTab->addManualUrl(lUrl);
}
void Mpris2Player::Pause()
{
Phonon::State lState = gMainWindow->mMediaObject->state();
if(lState == Phonon::PlayingState || lState == Phonon::BufferingState)
gMainWindow->togglePlayback();
}
void Mpris2Player::Play()
{
Phonon::State lState = gMainWindow->mMediaObject->state();
if(lState != Phonon::PlayingState && lState != Phonon::BufferingState)
gMainWindow->togglePlayback();
}
void Mpris2Player::PlayPause()
{
gMainWindow->togglePlayback();
}
void Mpris2Player::Previous()
{
gMainWindow->jumpToPreviousSong();
}
void Mpris2Player::Seek(qlonglong Offset)
{
gMainWindow->mMediaObject->seek(gMainWindow->mMediaObject->currentTime() + Offset * 1000);
}
void Mpris2Player::SetPosition(const QDBusObjectPath &TrackId, qlonglong Position)
{
if(!canSeek() || TrackId == QDBusObjectPath("org/mpris/MediaPlayer2/TrackList/NoTrack") ||
Position < 0 || Position > gMainWindow->mMediaObject->totalTime() * 1000) {
return;
}
gMainWindow->mMediaObject->seek(Position/1000);
}
void Mpris2Player::Stop()
{
Phonon::State lState = gMainWindow->mMediaObject->state();
if(lState == Phonon::PlayingState || lState == Phonon::BufferingState)
gMainWindow->togglePlayback();
gMainWindow->mMediaObject->seek(0);
}
void Mpris2Player::openM3U(const QString &pPath)
{
QFile lFile(pPath);
QString lReadPath;
QFileInfo lInfo(pPath);
QString lCurrentPath = lInfo.dir().absolutePath();
if(!lFile.open(QIODevice::ReadOnly | QIODevice::Text))
return;
QTextStream lTextStream(&lFile);
while(!lTextStream.atEnd())
{
lReadPath = lTextStream.readLine(4096).trimmed();
if(!lReadPath.startsWith('#'))
{
KUrl lUrl(lReadPath);
if(lUrl.protocol() == "http")
gMainWindow->mStreamsTab->addManualUrl(lUrl, lUrl.prettyUrl());
else
{
if(lUrl.isRelative())
lUrl = KUrl(lCurrentPath, lReadPath);
gMainWindow->mFileSystemTab->addManualUrl(lUrl);
}
}
}
}
void Mpris2Player::notifySeeked(qlonglong pTimeInUs) {
emit Seeked(pTimeInUs);
}
void Mpris2Player::openPLS(const QString &pPath)
{
QFile file(pPath);
if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
return;
QTextStream stream(&file);
// Better Handling of pls playlists - Taken from amaroK - amarok.kde.org
// Counted number of "File#=" lines.
uint entryCnt = 0;
// Value of the "NumberOfEntries=#" line.
uint numberOfEntries = 0;
bool havePlaylistSection = false;
QString tmp;
QStringList lines;
const QRegExp regExp_NumberOfEntries("^NumberOfEntries\\s*=\\s*\\d+$", Qt::CaseInsensitive);
const QRegExp regExp_File("^File\\d+\\s*=", Qt::CaseInsensitive);
const QRegExp regExp_Title("^Title\\d+\\s*=", Qt::CaseInsensitive);
const QRegExp regExp_Length("^Length\\d+\\s*=\\s*-?\\d+$", Qt::CaseInsensitive);
const QRegExp regExp_Version("^Version\\s*=\\s*\\d+$", Qt::CaseInsensitive);
const QString section_playlist("[playlist]");
/* Preprocess the input data.
* Read the lines into a buffer; Cleanup the line strings;
* Count the entries manually and read "NumberOfEntries".
*/
while (!stream.atEnd()) {
tmp = stream.readLine(4096).simplified();
if (tmp.isEmpty())
continue;
lines.append(tmp);
if (tmp == section_playlist) {
havePlaylistSection = true;
continue;
}
if (tmp.contains(regExp_File)) {
entryCnt++;
continue;
}
if (tmp.contains(regExp_NumberOfEntries)) {
numberOfEntries = tmp.section('=', -1).simplified().toUInt();
continue;
}
}
if (numberOfEntries != entryCnt)
numberOfEntries = entryCnt;
if(numberOfEntries == 0)
return;
uint index;
bool inPlaylistSection = false;
QString* files = new QString[entryCnt];
QString *titles = new QString[entryCnt];
QStringList::const_iterator i = lines.begin(), end = lines.end();
for( ; i != end; ++i)
{
if (!inPlaylistSection && havePlaylistSection) {
/* The playlist begins with the "[playlist]" tag.
* Skip everything before this.
*/
if ((*i) == section_playlist)
inPlaylistSection = true;
continue;
}
if ((*i).contains(regExp_File)) {
// Have a "File#=XYZ" line.
index = extractIndex(*i);
if (index <= numberOfEntries && index != 0)
files[index-1] = (*i).section('=', 1).trimmed();
}
if ((*i).contains(regExp_Title)) {
// Have a "Title#=XYZ" line.
index = extractIndex(*i);
if (index <= numberOfEntries && index != 0)
titles[index-1] = (*i).section('=', 1).trimmed();
}
}
QFileInfo lInfo(pPath);
QString lCurrentPath = lInfo.dir().absolutePath();
for (uint i=0; i<entryCnt; i++)
{
if(files[i].isEmpty())
continue;
KUrl lUrl(files[i]);
if(lUrl.protocol() == "http")
gMainWindow->mStreamsTab->addManualUrl(lUrl, titles[i]);
else
{
if(lUrl.isRelative())
lUrl = KUrl(lCurrentPath, files[i]);
gMainWindow->mFileSystemTab->addManualUrl(lUrl);
}
}
delete[] files;
delete[] titles;
}
uint Mpris2Player::extractIndex(const QString &pStr)
{
/* Extract the index number out of a .pls line.
* Example:
* extractIndex("File2=foobar") == 2
*/
bool ok = false;
unsigned int ret;
QString tmp(pStr.section('=', 0, 0));
tmp.remove(QRegExp("^\\D*"));
ret = tmp.simplified().toUInt(&ok);
if (!ok)
{
qWarning("error extracting index, corrupt pls file.");
ret = 0;
}
return ret;
}
void Mpris2Player::notifyChangedProperty(const QLatin1String &pPropertyName) {
QDBusMessage signal = QDBusMessage::createSignal("/org/mpris/MediaPlayer2",
"org.freedesktop.DBus.Properties",
"PropertiesChanged");
signal << QString("org.mpris.MediaPlayer2.Player");
QVariantMap lChanged;
lChanged.insert(pPropertyName, property(pPropertyName.latin1()));
signal << lChanged;
signal << QStringList();
QDBusConnection::sessionBus().send(signal);
}

135
boombox/src/mpris2player.h Normal file
View file

@ -0,0 +1,135 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef MPRIS2PLAYER_H
#define MPRIS2PLAYER_H
#include <QVariantMap>
#include <QDBusObjectPath>
#include <QStringList>
class Mpris2Player: public QObject
{
Q_OBJECT
public:
Mpris2Player(QObject *pParent);
public: // Properties for MediaPlayer2 interface.
Q_PROPERTY(bool CanQuit READ canQuit)
bool canQuit() const;
Q_PROPERTY(bool CanRaise READ canRaise)
bool canRaise() const;
Q_PROPERTY(QString DesktopEntry READ desktopEntry)
QString desktopEntry() const;
Q_PROPERTY(bool HasTrackList READ hasTrackList)
bool hasTrackList() const;
Q_PROPERTY(QString Identity READ identity)
QString identity() const;
Q_PROPERTY(QStringList SupportedMimeTypes READ supportedMimeTypes)
QStringList supportedMimeTypes() const;
Q_PROPERTY(QStringList SupportedUriSchemes READ supportedUriSchemes)
QStringList supportedUriSchemes() const;
public: // Properties for MediaPlayer2.Player interface.
Q_PROPERTY(bool CanControl READ canControl)
bool canControl() const;
Q_PROPERTY(bool CanGoNext READ canGoNext)
bool canGoNext() const;
Q_PROPERTY(bool CanGoPrevious READ canGoPrevious)
bool canGoPrevious() const;
Q_PROPERTY(bool CanPause READ canPause)
bool canPause() const;
Q_PROPERTY(bool CanPlay READ canPlay)
bool canPlay() const;
Q_PROPERTY(bool CanSeek READ canSeek)
bool canSeek() const;
Q_PROPERTY(QString LoopStatus READ loopStatus WRITE setLoopStatus)
QString loopStatus() const;
void setLoopStatus(const QString &value);
Q_PROPERTY(double MaximumRate READ maximumRate)
double maximumRate() const;
Q_PROPERTY(QVariantMap Metadata READ metadata)
QVariantMap metadata() const;
Q_PROPERTY(double MinimumRate READ minimumRate)
double minimumRate() const;
Q_PROPERTY(QString PlaybackStatus READ playbackStatus)
QString playbackStatus() const;
Q_PROPERTY(qlonglong Position READ position)
qlonglong position() const;
Q_PROPERTY(double Rate READ rate WRITE setRate)
double rate() const;
void setRate(double value);
Q_PROPERTY(bool Shuffle READ shuffle WRITE setShuffle)
bool shuffle() const;
void setShuffle(bool value);
Q_PROPERTY(double Volume READ volume WRITE setVolume)
double volume() const;
void setVolume(double value);
public slots: //Slots for MediaPlayer2 interface
void Quit();
void Raise();
public Q_SLOTS: // Slots for MediaPlayer2.Player interface.
void Next();
void OpenUri(const QString &Uri);
void Pause();
void Play();
void PlayPause();
void Previous();
void Seek(qlonglong Offset);
void SetPosition(const QDBusObjectPath &TrackId, qlonglong Position);
void Stop();
Q_SIGNALS: //Signals for MediaPlayer2.Player interface.
void Seeked(qlonglong Position);
public:
void notifyChangedProperty(const QLatin1String &pPropertyName);
public slots:
void openPLS(const QString &pPath);
void openM3U(const QString &pPath);
void notifySeeked(qlonglong pTimeInUs);
protected:
uint extractIndex(const QString &pStr);
};
#endif // MPRIS2PLAYER_H

128
boombox/src/threadjob.cpp Normal file
View file

@ -0,0 +1,128 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "threadjob.h"
#include <QApplication>
#include <QMetaType>
#include <QTimer>
void ThreadJobExecuter::run()
{
QTimer::singleShot(0, mJob, SLOT(doWork()));
connect(mJob, SIGNAL(finished(KJob*)), SLOT(quit()));
exec();
}
ThreadJob::ThreadJob()
:mShouldDie(false), mShouldSuspend(false), mHasStarted(false)
{
static bool onlyOnce = false;
if(!onlyOnce)
{
qRegisterMetaType<QPair<QString,QString> >("QPair<QString,QString>");
onlyOnce = true;
}
}
void ThreadJob::start()
{
mThread = new ThreadJobExecuter(this);
moveToThread(mThread);
mThread->start();
mHasStarted = true;
}
void ThreadJob::emitResult()
{
moveToThread(QApplication::instance()->thread());
KJob::emitResult();
}
bool ThreadJob::doKill()
{
if(!(capabilities() & Killable) || !mHasStarted)
return false;
mShouldDie = true;
return true;
}
bool ThreadJob::checkForDeath()
{
if(!mShouldDie)
QCoreApplication::processEvents();
if(mShouldDie)
{
aboutToDie();
mThread->quit();
moveToThread(QApplication::instance()->thread());
}
return mShouldDie;
}
bool ThreadJob::doSuspend()
{
if(!(capabilities() & Suspendable) || isSuspended())
return false;
mShouldSuspend = true;
return true;
}
bool ThreadJob::checkForDeathOrSuspend()
{
if(!mShouldSuspend && !mShouldDie)
QCoreApplication::processEvents();
if(mShouldDie)
{
aboutToDie();
mThread->quit();
moveToThread(QApplication::instance()->thread());
return true;
}
if(mShouldSuspend)
{
aboutToSuspend();
while(mShouldSuspend && !mShouldDie)
QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents);
if(mShouldDie)
{
aboutToDie();
mThread->quit();
moveToThread(QApplication::instance()->thread());
return true;
}
aboutToResume();
return false;
}
return false;
}
bool ThreadJob::doResume()
{
if(!(capabilities() & Suspendable) || !isSuspended())
return false;
mShouldSuspend = false;
return true;
}

71
boombox/src/threadjob.h Normal file
View file

@ -0,0 +1,71 @@
/***************************************************************************
* Copyright (C) by Simon Persson *
* simonop@spray.se *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef THREADJOB_H
#define THREADJOB_H
#include <QThread>
#include <KJob>
class ThreadJob;
class ThreadJobExecuter : public QThread
{
Q_OBJECT
public:
ThreadJobExecuter(ThreadJob *pJob) {mJob = pJob;}
void run();
private:
ThreadJob *mJob;
};
class ThreadJob: public KJob
{
Q_OBJECT
public:
ThreadJob();
virtual void start();
public slots:
virtual void doWork()=0;
protected:
virtual void emitResult();
virtual bool doKill();
virtual bool doSuspend();
virtual bool doResume();
virtual bool checkForDeathOrSuspend();
virtual bool checkForDeath();
virtual void aboutToDie(){}
virtual void aboutToSuspend(){}
virtual void aboutToResume(){}
ThreadJobExecuter *mThread;
bool mShouldDie;
bool mShouldSuspend;
bool mHasStarted;
};
#endif