mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-23 10:22:50 +00:00
boombox: import version 0.4.4
This commit is contained in:
parent
9b717eb5eb
commit
9a34fd6a9e
53 changed files with 7475 additions and 0 deletions
20
boombox/CMakeLists.txt
Normal file
20
boombox/CMakeLists.txt
Normal 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 )
|
1
boombox/icons/CMakeLists.txt
Normal file
1
boombox/icons/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
|||
kde4_install_icons( ${ICON_INSTALL_DIR} )
|
BIN
boombox/icons/hi16-app-boombox.png
Normal file
BIN
boombox/icons/hi16-app-boombox.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 846 B |
BIN
boombox/icons/hi32-app-boombox.png
Normal file
BIN
boombox/icons/hi32-app-boombox.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
BIN
boombox/icons/hi48-app-boombox.png
Normal file
BIN
boombox/icons/hi48-app-boombox.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
BIN
boombox/icons/hi64-app-boombox.png
Normal file
BIN
boombox/icons/hi64-app-boombox.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5 KiB |
BIN
boombox/icons/hisc-app-boombox.svgz
Normal file
BIN
boombox/icons/hisc-app-boombox.svgz
Normal file
Binary file not shown.
47
boombox/src/CMakeLists.txt
Normal file
47
boombox/src/CMakeLists.txt
Normal 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})
|
584
boombox/src/bbcollectiontab.cpp
Normal file
584
boombox/src/bbcollectiontab.cpp
Normal 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);
|
||||
}
|
||||
|
||||
|
126
boombox/src/bbcollectiontab.h
Normal file
126
boombox/src/bbcollectiontab.h
Normal 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
120
boombox/src/bbdatabase.cpp
Normal 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
71
boombox/src/bbdatabase.h
Normal 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
|
105
boombox/src/bbfilechangejob.cpp
Normal file
105
boombox/src/bbfilechangejob.cpp
Normal 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();
|
||||
}
|
43
boombox/src/bbfilechangejob.h
Normal file
43
boombox/src/bbfilechangejob.h
Normal 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
|
307
boombox/src/bbfilesystemtab.cpp
Normal file
307
boombox/src/bbfilesystemtab.cpp
Normal 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());
|
||||
}
|
91
boombox/src/bbfilesystemtab.h
Normal file
91
boombox/src/bbfilesystemtab.h
Normal 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
|
167
boombox/src/bbfilterdock.cpp
Normal file
167
boombox/src/bbfilterdock.cpp
Normal 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());
|
||||
}
|
73
boombox/src/bbfilterdock.h
Normal file
73
boombox/src/bbfilterdock.h
Normal 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
139
boombox/src/bblistitem.h
Normal 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
|
738
boombox/src/bbmainwindow.cpp
Normal file
738
boombox/src/bbmainwindow.cpp
Normal 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
188
boombox/src/bbmainwindow.h
Normal 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
141
boombox/src/bbmetadata.cpp
Normal 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
40
boombox/src/bbmetadata.h
Normal 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
|
438
boombox/src/bbmetainfodialog.cpp
Normal file
438
boombox/src/bbmetainfodialog.cpp
Normal 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.
|
||||
|
||||
}
|
||||
|
62
boombox/src/bbmetainfodialog.h
Normal file
62
boombox/src/bbmetainfodialog.h
Normal 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
|
261
boombox/src/bbmetainfodialog.ui
Normal file
261
boombox/src/bbmetainfodialog.ui
Normal 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>
|
3
boombox/src/bbplaylistsystem.cpp
Normal file
3
boombox/src/bbplaylistsystem.cpp
Normal file
|
@ -0,0 +1,3 @@
|
|||
#include "bbplaylistsystem.h"
|
||||
|
||||
//this dummy file will trick cmake to compile and link the moc file.
|
67
boombox/src/bbplaylistsystem.h
Normal file
67
boombox/src/bbplaylistsystem.h
Normal 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
|
243
boombox/src/bbresultview.cpp
Normal file
243
boombox/src/bbresultview.cpp
Normal 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());
|
||||
}
|
||||
|
64
boombox/src/bbresultview.h
Normal file
64
boombox/src/bbresultview.h
Normal 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
552
boombox/src/bbsetmodel.cpp
Normal 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
120
boombox/src/bbsetmodel.h
Normal 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
|
7
boombox/src/bbsettings.kcfgc
Normal file
7
boombox/src/bbsettings.kcfgc
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Code generation options for kconfig_compiler
|
||||
File=boombox.kcfg
|
||||
ClassName=BBSettings
|
||||
Singleton=true
|
||||
Mutators=true
|
||||
ItemAccessors=true
|
||||
SetUserTexts=true
|
53
boombox/src/bbstreameditform.cpp
Normal file
53
boombox/src/bbstreameditform.cpp
Normal 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();
|
||||
}
|
50
boombox/src/bbstreameditform.h
Normal file
50
boombox/src/bbstreameditform.h
Normal 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
|
95
boombox/src/bbstreameditform.ui
Normal file
95
boombox/src/bbstreameditform.ui
Normal 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>
|
719
boombox/src/bbstreamstab.cpp
Normal file
719
boombox/src/bbstreamstab.cpp
Normal 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
113
boombox/src/bbstreamstab.h
Normal 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
|
15
boombox/src/boombox.desktop
Normal file
15
boombox/src/boombox.desktop
Normal 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
31
boombox/src/boombox.kcfg
Normal 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
179
boombox/src/dbqueryjob.cpp
Normal 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
114
boombox/src/dbqueryjob.h
Normal 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
201
boombox/src/dbupdatejob.cpp
Normal 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
48
boombox/src/dbupdatejob.h
Normal 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
|
43
boombox/src/dbus/org.mpris.MediaPlayer2.Player.xml
Normal file
43
boombox/src/dbus/org.mpris.MediaPlayer2.Player.xml
Normal 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>
|
45
boombox/src/dbus/org.mpris.MediaPlayer2.TrackList.xml
Normal file
45
boombox/src/dbus/org.mpris.MediaPlayer2.TrackList.xml
Normal 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>
|
16
boombox/src/dbus/org.mpris.MediaPlayer2.xml
Normal file
16
boombox/src/dbus/org.mpris.MediaPlayer2.xml
Normal 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>
|
48
boombox/src/directoriespage.ui
Normal file
48
boombox/src/directoriespage.ui
Normal 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
88
boombox/src/main.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
465
boombox/src/mpris2player.cpp
Normal file
465
boombox/src/mpris2player.cpp
Normal 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
135
boombox/src/mpris2player.h
Normal 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
128
boombox/src/threadjob.cpp
Normal 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
71
boombox/src/threadjob.h
Normal 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
|
Loading…
Add table
Reference in a new issue