mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-23 18:32:51 +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