[UI] Added playlist editing features
This commit is contained in:
@@ -20,7 +20,7 @@ ui = {
|
|||||||
enable = true;
|
enable = true;
|
||||||
|
|
||||||
resources = {
|
resources = {
|
||||||
docroot = "/usr/share/Wt/"
|
docroot = "/var/lms/docroot"
|
||||||
approot = "/var/lms/approot"
|
approot = "/var/lms/approot"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Emeric Poupon
|
||||||
|
*
|
||||||
|
* This file is part of LMS.
|
||||||
|
*
|
||||||
|
* LMS 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 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "AudioTypes.hpp"
|
||||||
|
|
||||||
|
namespace Database {
|
||||||
|
|
||||||
|
Playlist::Playlist()
|
||||||
|
: _public(false)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Playlist::Playlist(std::string name, Wt::Dbo::ptr<User> user, bool isPublic)
|
||||||
|
: _public(isPublic),
|
||||||
|
_user(user)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Database
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Emeric Poupon
|
||||||
|
*
|
||||||
|
* This file is part of LMS.
|
||||||
|
*
|
||||||
|
* LMS 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 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "AudioTypes.hpp"
|
||||||
|
|
||||||
|
namespace Database {
|
||||||
|
|
||||||
|
PlaylistEntry::PlaylistEntry()
|
||||||
|
: _position(0)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PlaylistEntry::PlaylistEntry(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Playlist> playlist)
|
||||||
|
: _position(0),
|
||||||
|
_track(track),
|
||||||
|
_playlist(playlist)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Database
|
||||||
@@ -76,6 +76,8 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, boost::filesystem::p
|
|||||||
bootstrapTheme->setResponsive(true);
|
bootstrapTheme->setResponsive(true);
|
||||||
setTheme(bootstrapTheme);
|
setTheme(bootstrapTheme);
|
||||||
|
|
||||||
|
useStyleSheet("css/lms.css");
|
||||||
|
|
||||||
// Add a resource bundle
|
// Add a resource bundle
|
||||||
messageResourceBundle().use(appRoot() + "templates");
|
messageResourceBundle().use(appRoot() + "templates");
|
||||||
|
|
||||||
|
|||||||
+120
-66
@@ -24,14 +24,13 @@
|
|||||||
#include <Wt/WGridLayout>
|
#include <Wt/WGridLayout>
|
||||||
#include <Wt/WComboBox>
|
#include <Wt/WComboBox>
|
||||||
#include <Wt/WPushButton>
|
#include <Wt/WPushButton>
|
||||||
|
#include <Wt/WPopupMenu>
|
||||||
|
|
||||||
#include "logger/Logger.hpp"
|
#include "logger/Logger.hpp"
|
||||||
|
|
||||||
#include "TableFilter.hpp"
|
#include "TableFilter.hpp"
|
||||||
#include "TableFilterGenre.hpp"
|
#include "TableFilterGenre.hpp"
|
||||||
#include "KeywordSearchFilter.hpp"
|
#include "KeywordSearchFilter.hpp"
|
||||||
#include "TrackView.hpp"
|
|
||||||
#include "PlayQueue.hpp"
|
|
||||||
|
|
||||||
#include "Audio.hpp"
|
#include "Audio.hpp"
|
||||||
|
|
||||||
@@ -40,41 +39,37 @@ namespace UserInterface {
|
|||||||
Audio::Audio(SessionData& sessionData, Wt::WContainerWidget* parent)
|
Audio::Audio(SessionData& sessionData, Wt::WContainerWidget* parent)
|
||||||
: Wt::WContainerWidget(parent),
|
: Wt::WContainerWidget(parent),
|
||||||
_db(sessionData.getDatabaseHandler()),
|
_db(sessionData.getDatabaseHandler()),
|
||||||
_mediaPlayer(nullptr)
|
_mediaPlayer(nullptr),
|
||||||
|
_trackView(nullptr),
|
||||||
|
_playQueue(nullptr)
|
||||||
{
|
{
|
||||||
|
|
||||||
Wt::WGridLayout *mainLayout = new Wt::WGridLayout();
|
Wt::WGridLayout *mainLayout = new Wt::WGridLayout();
|
||||||
this->setLayout(mainLayout);
|
this->setLayout(mainLayout);
|
||||||
|
|
||||||
// Filters
|
// Filters
|
||||||
Wt::WHBoxLayout *filterLayout = new Wt::WHBoxLayout();
|
Wt::WHBoxLayout *filterLayout = new Wt::WHBoxLayout();
|
||||||
|
|
||||||
{
|
TableFilterGenre *filterGenre = new TableFilterGenre(_db);
|
||||||
TableFilterGenre *filter = new TableFilterGenre(_db);
|
filterLayout->addWidget(filterGenre);
|
||||||
filterLayout->addWidget(filter);
|
_filterChain.addFilter(filterGenre);
|
||||||
_filterChain.addFilter(filter);
|
|
||||||
}
|
TableFilter *filterArtist = new TableFilter(_db, "track", "artist_name", "Artist");
|
||||||
{
|
filterLayout->addWidget(filterArtist);
|
||||||
TableFilter *filter = new TableFilter(_db, "track", "artist_name");
|
_filterChain.addFilter(filterArtist);
|
||||||
filterLayout->addWidget(filter);
|
|
||||||
_filterChain.addFilter(filter);
|
TableFilter *filterRelease = new TableFilter(_db, "track", "release_name", "Release");
|
||||||
}
|
filterLayout->addWidget(filterRelease);
|
||||||
{
|
_filterChain.addFilter(filterRelease);
|
||||||
TableFilter *filter = new TableFilter(_db, "track", "release_name");
|
|
||||||
filterLayout->addWidget(filter);
|
|
||||||
_filterChain.addFilter(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
mainLayout->addLayout(filterLayout, 0, 0);
|
mainLayout->addLayout(filterLayout, 0, 0);
|
||||||
// ENDOF(Filters)
|
// ENDOF(Filters)
|
||||||
|
|
||||||
// TODO ADD controls (Play, add)
|
|
||||||
// TODO ADD some stats (nb files, total duration, etc.)
|
// TODO ADD some stats (nb files, total duration, etc.)
|
||||||
|
|
||||||
Wt::WVBoxLayout* trackLayout = new Wt::WVBoxLayout();
|
Wt::WVBoxLayout* trackLayout = new Wt::WVBoxLayout();
|
||||||
|
|
||||||
TrackView* trackView = new TrackView(_db);
|
_trackView = new TrackView(_db);
|
||||||
trackLayout->addWidget(trackView, 1);
|
trackLayout->addWidget(_trackView, 1);
|
||||||
|
|
||||||
Wt::WHBoxLayout* trackControls = new Wt::WHBoxLayout();
|
Wt::WHBoxLayout* trackControls = new Wt::WHBoxLayout();
|
||||||
|
|
||||||
@@ -90,9 +85,9 @@ _mediaPlayer(nullptr)
|
|||||||
|
|
||||||
mainLayout->addLayout(trackLayout, 1, 0);
|
mainLayout->addLayout(trackLayout, 1, 0);
|
||||||
|
|
||||||
_filterChain.addFilter(trackView);
|
_filterChain.addFilter(_trackView);
|
||||||
|
|
||||||
PlayQueue *playQueue = new PlayQueue(_db);
|
_playQueue = new PlayQueue(_db);
|
||||||
|
|
||||||
// Playlist/PlayQueue
|
// Playlist/PlayQueue
|
||||||
{
|
{
|
||||||
@@ -103,12 +98,49 @@ _mediaPlayer(nullptr)
|
|||||||
|
|
||||||
Wt::WHBoxLayout* playlistControls = new Wt::WHBoxLayout();
|
Wt::WHBoxLayout* playlistControls = new Wt::WHBoxLayout();
|
||||||
|
|
||||||
playlistControls->addWidget(new Wt::WPushButton("Playlist"));
|
Wt::WPushButton *saveBtn = new Wt::WPushButton("Save");
|
||||||
playlistControls->addWidget(new Wt::WText("Duration:"));
|
playlistControls->addWidget(saveBtn);
|
||||||
|
Wt::WPushButton *loadBtn = new Wt::WPushButton("Load");
|
||||||
|
playlistControls->addWidget(loadBtn);
|
||||||
|
|
||||||
|
Wt::WPushButton *upBtn = new Wt::WPushButton("UP");
|
||||||
|
playlistControls->addWidget(upBtn);
|
||||||
|
Wt::WPushButton *downBtn = new Wt::WPushButton("DO");
|
||||||
|
playlistControls->addWidget(downBtn);
|
||||||
|
Wt::WPushButton *delBtn = new Wt::WPushButton("DEL");
|
||||||
|
playlistControls->addWidget(delBtn);
|
||||||
|
|
||||||
|
delBtn->clicked().connect(_playQueue, &PlayQueue::delSelected);
|
||||||
|
upBtn->clicked().connect(_playQueue, &PlayQueue::moveSelectedUp);
|
||||||
|
downBtn->clicked().connect(_playQueue, &PlayQueue::moveSelectedDown);
|
||||||
|
|
||||||
|
playlistControls->addWidget(new Wt::WText("Duration:"), 1);
|
||||||
|
|
||||||
|
// Load menu
|
||||||
|
{
|
||||||
|
Wt::WPopupMenu *popup = new Wt::WPopupMenu();
|
||||||
|
|
||||||
|
popup->addItem("Metal");
|
||||||
|
popup->addItem("Test");
|
||||||
|
|
||||||
|
loadBtn->setMenu(popup);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save Menu
|
||||||
|
{
|
||||||
|
Wt::WPopupMenu *popup = new Wt::WPopupMenu();
|
||||||
|
|
||||||
|
popup->addItem("New");
|
||||||
|
popup->addSeparator();
|
||||||
|
popup->addItem("Metal");
|
||||||
|
popup->addItem("Test");
|
||||||
|
|
||||||
|
saveBtn->setMenu(popup);
|
||||||
|
}
|
||||||
|
|
||||||
playQueueLayout->addLayout(playlistControls);
|
playQueueLayout->addLayout(playlistControls);
|
||||||
|
|
||||||
playQueueLayout->addWidget( playQueue, 1);
|
playQueueLayout->addWidget( _playQueue, 1);
|
||||||
|
|
||||||
mainLayout->addLayout(playQueueLayout, 0, 1, 2, 1);
|
mainLayout->addLayout(playQueueLayout, 0, 1, 2, 1);
|
||||||
}
|
}
|
||||||
@@ -122,54 +154,27 @@ _mediaPlayer(nullptr)
|
|||||||
|
|
||||||
// Double click on track
|
// Double click on track
|
||||||
// Set the selected tracks to the play queue
|
// Set the selected tracks to the play queue
|
||||||
trackView->trackDoubleClicked().connect(std::bind([=] ()
|
_trackView->trackDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddSelectedTracks));
|
||||||
{
|
|
||||||
std::vector<Database::Track::id_type> trackIds;
|
|
||||||
trackView->getSelectedTracks(trackIds);
|
|
||||||
|
|
||||||
playQueue->clear();
|
// Double click on artist
|
||||||
playQueue->addTracks(trackIds);
|
// Set the selected tracks to the play queue
|
||||||
|
filterArtist->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks));
|
||||||
playQueue->play();
|
filterRelease->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks));
|
||||||
|
filterGenre->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks));
|
||||||
}));
|
|
||||||
|
|
||||||
// Play button
|
// Play button
|
||||||
// Set the selected tracks to the play queue
|
// Set the selected tracks to the play queue
|
||||||
playBtn->clicked().connect(std::bind([=] ()
|
playBtn->clicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddSelectedTracks));
|
||||||
{
|
|
||||||
std::vector<Database::Track::id_type> trackIds;
|
|
||||||
trackView->getSelectedTracks(trackIds);
|
|
||||||
|
|
||||||
// If nothing is selected, get the whole track list
|
|
||||||
if (trackIds.empty())
|
|
||||||
trackView->getTracks(trackIds);
|
|
||||||
|
|
||||||
playQueue->clear();
|
|
||||||
playQueue->addTracks(trackIds);
|
|
||||||
|
|
||||||
playQueue->play();
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Add Button
|
// Add Button
|
||||||
// Add the selected tracks at the end of the play queue
|
// Add the selected tracks at the end of the play queue
|
||||||
addBtn->clicked().connect(std::bind([=] ()
|
addBtn->clicked().connect(this, &Audio::addSelectedTracks);
|
||||||
{
|
|
||||||
std::vector<Database::Track::id_type> trackIds;
|
|
||||||
trackView->getSelectedTracks(trackIds);
|
|
||||||
|
|
||||||
// If nothing is selected, get the whole track list
|
_playQueue->playTrack().connect(this, &Audio::playTrack);
|
||||||
if (trackIds.empty())
|
|
||||||
trackView->getTracks(trackIds);
|
|
||||||
|
|
||||||
playQueue->addTracks(trackIds);
|
_mediaPlayer->playbackEnded().connect(_playQueue, &PlayQueue::handlePlaybackComplete);
|
||||||
}));
|
_mediaPlayer->playNext().connect(_playQueue, &PlayQueue::playNext);
|
||||||
|
_mediaPlayer->playPrevious().connect(_playQueue, &PlayQueue::playPrevious);
|
||||||
playQueue->playTrack().connect(this, &Audio::playTrack);
|
|
||||||
|
|
||||||
_mediaPlayer->playbackEnded().connect(playQueue, &PlayQueue::handlePlaybackComplete);
|
|
||||||
_mediaPlayer->playNext().connect(playQueue, &PlayQueue::playNext);
|
|
||||||
_mediaPlayer->playPrevious().connect(playQueue, &PlayQueue::playPrevious);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -178,6 +183,55 @@ Audio::search(const std::string& searchText)
|
|||||||
_filterChain.searchKeyword(searchText);
|
_filterChain.searchKeyword(searchText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Audio::playSelectedTracks(PlayQueueAddType addType)
|
||||||
|
{
|
||||||
|
int firstTrackPos = 0;
|
||||||
|
std::vector<Database::Track::id_type> trackIds;
|
||||||
|
|
||||||
|
switch(addType)
|
||||||
|
{
|
||||||
|
case PlayQueueAddAllTracks:
|
||||||
|
// Play all the tracks, from the first one
|
||||||
|
firstTrackPos = 0;
|
||||||
|
_trackView->getTracks(trackIds);
|
||||||
|
break;
|
||||||
|
case PlayQueueAddSelectedTracks:
|
||||||
|
// If nothing or only ONE selected, get them all
|
||||||
|
if (_trackView->getNbSelectedTracks() <= 1)
|
||||||
|
{
|
||||||
|
// Start to play at the selected track, if any
|
||||||
|
firstTrackPos = _trackView->getFirstSelectedTrackPosition();
|
||||||
|
_trackView->getTracks(trackIds);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Play all the selected tracks, frm the first one
|
||||||
|
firstTrackPos = 0;
|
||||||
|
_trackView->getSelectedTracks(trackIds);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_playQueue->clear();
|
||||||
|
_playQueue->addTracks(trackIds);
|
||||||
|
|
||||||
|
_playQueue->play(firstTrackPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Audio::addSelectedTracks(void)
|
||||||
|
{
|
||||||
|
std::vector<Database::Track::id_type> trackIds;
|
||||||
|
_trackView->getSelectedTracks(trackIds);
|
||||||
|
|
||||||
|
// If nothing is selected, get the whole track list
|
||||||
|
if (trackIds.empty())
|
||||||
|
_trackView->getTracks(trackIds);
|
||||||
|
|
||||||
|
_playQueue->addTracks(trackIds);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Audio::playTrack(boost::filesystem::path p)
|
Audio::playTrack(boost::filesystem::path p)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,6 +27,8 @@
|
|||||||
#include "common/SessionData.hpp"
|
#include "common/SessionData.hpp"
|
||||||
|
|
||||||
#include "AudioMediaPlayer.hpp"
|
#include "AudioMediaPlayer.hpp"
|
||||||
|
#include "TrackView.hpp"
|
||||||
|
#include "PlayQueue.hpp"
|
||||||
|
|
||||||
#include "FilterChain.hpp"
|
#include "FilterChain.hpp"
|
||||||
|
|
||||||
@@ -45,11 +47,22 @@ class Audio : public Wt::WContainerWidget
|
|||||||
|
|
||||||
void playTrack(boost::filesystem::path p);
|
void playTrack(boost::filesystem::path p);
|
||||||
|
|
||||||
|
enum PlayQueueAddType
|
||||||
|
{
|
||||||
|
PlayQueueAddAllTracks,
|
||||||
|
PlayQueueAddSelectedTracks,
|
||||||
|
};
|
||||||
|
|
||||||
|
void playSelectedTracks(PlayQueueAddType addType);
|
||||||
|
void addSelectedTracks();
|
||||||
|
|
||||||
void handlePlaylistSelected(Wt::WString name);
|
void handlePlaylistSelected(Wt::WString name);
|
||||||
|
|
||||||
Database::Handler& _db;
|
Database::Handler& _db;
|
||||||
|
|
||||||
AudioMediaPlayer* _mediaPlayer;
|
AudioMediaPlayer* _mediaPlayer;
|
||||||
|
TrackView* _trackView;
|
||||||
|
PlayQueue* _playQueue;
|
||||||
|
|
||||||
FilterChain _filterChain;
|
FilterChain _filterChain;
|
||||||
|
|
||||||
|
|||||||
@@ -63,11 +63,13 @@ AudioMediaPlayer::AudioMediaPlayer( Wt::WContainerWidget *parent)
|
|||||||
|
|
||||||
nextBtn->clicked().connect(std::bind([=] ()
|
nextBtn->clicked().connect(std::bind([=] ()
|
||||||
{
|
{
|
||||||
|
_mediaPlayer->stop();
|
||||||
_playNext.emit();
|
_playNext.emit();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
prevBtn->clicked().connect(std::bind([=] ()
|
prevBtn->clicked().connect(std::bind([=] ()
|
||||||
{
|
{
|
||||||
|
_mediaPlayer->stop();
|
||||||
_playPrevious.emit();
|
_playPrevious.emit();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
+186
-10
@@ -17,15 +17,64 @@
|
|||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <Wt/WItemDelegate>
|
||||||
|
|
||||||
|
#include "logger/Logger.hpp"
|
||||||
|
|
||||||
#include "PlayQueue.hpp"
|
#include "PlayQueue.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void modelForceRefreshDataRow(Wt::WStandardItemModel *model, int row)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < model->columnCount(); ++i)
|
||||||
|
model->setData(row, i, model->data(row, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
void swapRows(Wt::WStandardItemModel *model, int row1, int row2)
|
||||||
|
{
|
||||||
|
// Copy column by column
|
||||||
|
for (int i = 0; i < model->columnCount(); ++i)
|
||||||
|
{
|
||||||
|
boost::any tmp = model->data(row1, i); // 1 -> tmp
|
||||||
|
model->setData(row1, i, model->data(row2, i)); // 2 -> 1
|
||||||
|
model->setData(row2, i, tmp); // tmp-> 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
|
|
||||||
|
static const int trackIdInvalid = -1;
|
||||||
|
|
||||||
|
class PlayQueueItemDelegate : public Wt::WItemDelegate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PlayQueueItemDelegate(Wt::WObject *parent = 0) : Wt::WItemDelegate(parent), _selectedRowId(trackIdInvalid) {}
|
||||||
|
|
||||||
|
void setSelectedRowId(int rowId) { _selectedRowId = rowId; }
|
||||||
|
|
||||||
|
Wt::WWidget* update(Wt::WWidget *widget, const Wt::WModelIndex &index, Wt::WFlags< Wt::ViewItemRenderFlag > flags)
|
||||||
|
{
|
||||||
|
Wt::WWidget* res = Wt::WItemDelegate::update(widget, index, flags);
|
||||||
|
|
||||||
|
if (res && index.isValid() && index.row() == _selectedRowId)
|
||||||
|
res->toggleStyleClass("playqueue-playing", true);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
int _selectedRowId;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
PlayQueue::PlayQueue(Database::Handler& db, Wt::WContainerWidget* parent)
|
PlayQueue::PlayQueue(Database::Handler& db, Wt::WContainerWidget* parent)
|
||||||
: Wt::WTableView(parent),
|
: Wt::WTableView(parent),
|
||||||
_db(db),
|
_db(db),
|
||||||
_playedId(-1)
|
_playedId(trackIdInvalid)
|
||||||
{
|
{
|
||||||
_model = new Wt::WStandardItemModel(0, 4, this);
|
_model = new Wt::WStandardItemModel(0, 4, this);
|
||||||
|
|
||||||
@@ -45,6 +94,9 @@ _playedId(-1)
|
|||||||
|
|
||||||
this->setColumnHidden(0, true);
|
this->setColumnHidden(0, true);
|
||||||
|
|
||||||
|
_itemDelegate = new PlayQueueItemDelegate();
|
||||||
|
this->setItemDelegate(_itemDelegate);
|
||||||
|
|
||||||
this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt)
|
this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt)
|
||||||
{
|
{
|
||||||
if (!idx.isValid())
|
if (!idx.isValid())
|
||||||
@@ -64,9 +116,9 @@ _playedId(-1)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
PlayQueue::play(void)
|
PlayQueue::play(int trackId)
|
||||||
{
|
{
|
||||||
readTrack(0);
|
readTrack(trackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -99,23 +151,21 @@ PlayQueue::clear(void)
|
|||||||
_model->removeRows(0, _model->rowCount());
|
_model->removeRows(0, _model->rowCount());
|
||||||
|
|
||||||
// Reset play id
|
// Reset play id
|
||||||
_playedId = -1;
|
_playedId = trackIdInvalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
PlayQueue::handlePlaybackComplete(void)
|
PlayQueue::handlePlaybackComplete(void)
|
||||||
{
|
{
|
||||||
// Play the next track
|
// Play the next track
|
||||||
if (_playedId != -1)
|
if (_playedId != trackIdInvalid)
|
||||||
{
|
|
||||||
readTrack(_playedId + 1);
|
readTrack(_playedId + 1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
PlayQueue::playNext(void)
|
PlayQueue::playNext(void)
|
||||||
{
|
{
|
||||||
readTrack( _playedId != -1 ? _playedId + 1 : 0);
|
readTrack( _playedId != trackIdInvalid ? _playedId + 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -136,15 +186,141 @@ PlayQueue::readTrack(int rowId)
|
|||||||
Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId);
|
Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId);
|
||||||
if (track)
|
if (track)
|
||||||
{
|
{
|
||||||
_playedId = rowId;
|
setPlayingTrackId(rowId);
|
||||||
|
|
||||||
_sigTrackPlay.emit(track->getPath());
|
_sigTrackPlay.emit(track->getPath());
|
||||||
break;
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
++rowId;
|
++rowId;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PlayQueue::setPlayingTrackId(int newRowId)
|
||||||
|
{
|
||||||
|
int oldRowId = _playedId;
|
||||||
|
_playedId = newRowId;
|
||||||
|
|
||||||
|
_itemDelegate->setSelectedRowId(newRowId);
|
||||||
|
|
||||||
|
// Hack re-set the data in order to trigger the rerending of the widget
|
||||||
|
// calling the update method of our custom item delegate gives bad results
|
||||||
|
if (oldRowId >= 0)
|
||||||
|
modelForceRefreshDataRow(_model, oldRowId);
|
||||||
|
|
||||||
|
if (newRowId >= 0)
|
||||||
|
modelForceRefreshDataRow(_model, newRowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PlayQueue::delSelected(void)
|
||||||
|
{
|
||||||
|
int minId = _model->rowCount();
|
||||||
|
Wt::WModelIndexSet indexSet = this->selectedIndexes();
|
||||||
|
|
||||||
|
BOOST_REVERSE_FOREACH(Wt::WModelIndex index, indexSet)
|
||||||
|
{
|
||||||
|
_model->removeRow(index.row());
|
||||||
|
if (index.row() < minId)
|
||||||
|
minId = index.row();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the current played track is removed, make sure to unselect it
|
||||||
|
_itemDelegate->setSelectedRowId(trackIdInvalid);
|
||||||
|
|
||||||
|
renumber(minId, _model->rowCount() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PlayQueue::moveSelectedUp(void)
|
||||||
|
{
|
||||||
|
int minId = _model->rowCount();
|
||||||
|
int maxId = 0;
|
||||||
|
|
||||||
|
Wt::WModelIndexSet indexSet = this->selectedIndexes();
|
||||||
|
Wt::WModelIndexSet newIndexSet;
|
||||||
|
|
||||||
|
// ordered from up to down
|
||||||
|
BOOST_FOREACH(Wt::WModelIndex index, indexSet)
|
||||||
|
{
|
||||||
|
// Do nothing if the first selected index is on the top
|
||||||
|
if (index.row() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// TODO optimize for blocks
|
||||||
|
swapRows(_model, index.row() - 1, index.row());
|
||||||
|
|
||||||
|
if (index.row() - 1 < minId)
|
||||||
|
minId = index.row() - 1;
|
||||||
|
|
||||||
|
if (index.row() > maxId)
|
||||||
|
maxId = index.row();
|
||||||
|
|
||||||
|
// Update playing status
|
||||||
|
if (_playedId == index.row())
|
||||||
|
setPlayingTrackId(_playedId - 1);
|
||||||
|
else if (_playedId == index.row() - 1)
|
||||||
|
setPlayingTrackId(_playedId + 1);
|
||||||
|
|
||||||
|
newIndexSet.insert( _model->index(index.row() - 1, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
this->setSelectedIndexes( newIndexSet );
|
||||||
|
|
||||||
|
renumber(minId, maxId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
PlayQueue::moveSelectedDown(void)
|
||||||
|
{
|
||||||
|
int minId = _model->rowCount();
|
||||||
|
int maxId = 0;
|
||||||
|
|
||||||
|
Wt::WModelIndexSet indexSet = this->selectedIndexes();
|
||||||
|
Wt::WModelIndexSet newIndexSet;
|
||||||
|
|
||||||
|
// ordered from up to down
|
||||||
|
BOOST_REVERSE_FOREACH(Wt::WModelIndex index, indexSet)
|
||||||
|
{
|
||||||
|
// Do nothing if the last selected index is the last one
|
||||||
|
if (index.row() == _model->rowCount() - 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// TODO optimize for blocks
|
||||||
|
swapRows(_model, index.row(), index.row() + 1);
|
||||||
|
|
||||||
|
if (index.row() < minId)
|
||||||
|
minId = index.row();
|
||||||
|
|
||||||
|
if (index.row() + 1 > maxId)
|
||||||
|
maxId = index.row() + 1;
|
||||||
|
|
||||||
|
// Update playing status
|
||||||
|
if (_playedId == index.row())
|
||||||
|
setPlayingTrackId(_playedId + 1);
|
||||||
|
else if (_playedId == index.row() + 1)
|
||||||
|
setPlayingTrackId(_playedId - 1);
|
||||||
|
|
||||||
|
newIndexSet.insert( _model->index(index.row() + 1, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
this->setSelectedIndexes( newIndexSet );
|
||||||
|
|
||||||
|
renumber(minId, maxId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PlayQueue::renumber(int firstId, int lastId)
|
||||||
|
{
|
||||||
|
for (int i = firstId; i <= lastId; ++i)
|
||||||
|
_model->setData( i, 1, i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
|
|
||||||
|
class PlayQueueItemDelegate;
|
||||||
|
|
||||||
class PlayQueue : public Wt::WTableView
|
class PlayQueue : public Wt::WTableView
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -37,10 +39,16 @@ class PlayQueue : public Wt::WTableView
|
|||||||
|
|
||||||
void clear(void);
|
void clear(void);
|
||||||
|
|
||||||
void play(void); // Play the queue from the beginning
|
// Play functions
|
||||||
|
void play(int trackId = 0); // Play the queue
|
||||||
void playNext(void); // Play the next track
|
void playNext(void); // Play the next track
|
||||||
void playPrevious(void); // Play the previous track
|
void playPrevious(void); // Play the previous track
|
||||||
|
|
||||||
|
// List manipulations
|
||||||
|
void delSelected(void);
|
||||||
|
void moveSelectedUp(void);
|
||||||
|
void moveSelectedDown(void);
|
||||||
|
|
||||||
// Signals
|
// Signals
|
||||||
Wt::Signal< boost::filesystem::path >& playTrack() { return _sigTrackPlay; }
|
Wt::Signal< boost::filesystem::path >& playTrack() { return _sigTrackPlay; }
|
||||||
|
|
||||||
@@ -52,6 +60,8 @@ class PlayQueue : public Wt::WTableView
|
|||||||
private:
|
private:
|
||||||
|
|
||||||
void readTrack(int rowId);
|
void readTrack(int rowId);
|
||||||
|
void setPlayingTrackId(int newRowId);
|
||||||
|
void renumber(int firstId, int lastId);
|
||||||
|
|
||||||
Wt::Signal< boost::filesystem::path > _sigTrackPlay;
|
Wt::Signal< boost::filesystem::path > _sigTrackPlay;
|
||||||
|
|
||||||
@@ -59,6 +69,8 @@ class PlayQueue : public Wt::WTableView
|
|||||||
|
|
||||||
Wt::WStandardItemModel* _model;
|
Wt::WStandardItemModel* _model;
|
||||||
|
|
||||||
|
PlayQueueItemDelegate* _itemDelegate;
|
||||||
|
|
||||||
int _playedId;
|
int _playedId;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
|
|
||||||
TableFilter::TableFilter(Database::Handler& db, std::string table, std::string field, Wt::WContainerWidget* parent)
|
TableFilter::TableFilter(Database::Handler& db, std::string table, std::string field, const Wt::WString& displayName, Wt::WContainerWidget* parent)
|
||||||
: Wt::WTableView( parent ),
|
: Wt::WTableView( parent ),
|
||||||
Filter(),
|
Filter(),
|
||||||
_db(db),
|
_db(db),
|
||||||
@@ -33,8 +33,8 @@ _table(table),
|
|||||||
_field(field)
|
_field(field)
|
||||||
{
|
{
|
||||||
_queryModel.setQuery( _db.getSession().query< ResultType >("select track." + _field + ", COUNT(DISTINCT track.id) from track GROUP BY track." + _field).orderBy("track." + _field));
|
_queryModel.setQuery( _db.getSession().query< ResultType >("select track." + _field + ", COUNT(DISTINCT track.id) from track GROUP BY track." + _field).orderBy("track." + _field));
|
||||||
_queryModel.addColumn( "track." + _field, field);
|
_queryModel.addColumn( "track." + _field, displayName);
|
||||||
_queryModel.addColumn( "COUNT(DISTINCT track.id)", "tracks");
|
_queryModel.addColumn( "COUNT(DISTINCT track.id)", "Tracks");
|
||||||
|
|
||||||
this->setSelectionMode(Wt::ExtendedSelection);
|
this->setSelectionMode(Wt::ExtendedSelection);
|
||||||
this->setSortingEnabled(false);
|
this->setSortingEnabled(false);
|
||||||
@@ -46,6 +46,21 @@ _field(field)
|
|||||||
setLayoutSizeAware(true);
|
setLayoutSizeAware(true);
|
||||||
|
|
||||||
_queryModel.setBatchSize(100);
|
_queryModel.setBatchSize(100);
|
||||||
|
|
||||||
|
// If an item is double clicked, select and emit signal
|
||||||
|
this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt)
|
||||||
|
{
|
||||||
|
if (!idx.isValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Wt::WModelIndexSet indexSet;
|
||||||
|
indexSet.insert(idx);
|
||||||
|
|
||||||
|
this->setSelectedIndexes( indexSet );
|
||||||
|
_sigDoubleClicked.emit( );
|
||||||
|
|
||||||
|
}, std::placeholders::_1, std::placeholders::_2));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class TableFilter : public Wt::WTableView, public Filter
|
|||||||
{
|
{
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TableFilter(Database::Handler& db, std::string table, std::string field, Wt::WContainerWidget* parent = 0);
|
TableFilter(Database::Handler& db, std::string table, std::string field, const Wt::WString& displayName, Wt::WContainerWidget* parent = 0);
|
||||||
|
|
||||||
// Set constraints on this filter
|
// Set constraints on this filter
|
||||||
void refresh(const Constraint& constraint);
|
void refresh(const Constraint& constraint);
|
||||||
@@ -43,8 +43,14 @@ class TableFilter : public Wt::WTableView, public Filter
|
|||||||
|
|
||||||
void layoutSizeChanged (int width, int height);
|
void layoutSizeChanged (int width, int height);
|
||||||
|
|
||||||
|
typedef Wt::Signal<void> SigDoubleClicked;
|
||||||
|
|
||||||
|
SigDoubleClicked& sigDoubleClicked() { return _sigDoubleClicked; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
SigDoubleClicked _sigDoubleClicked;
|
||||||
|
|
||||||
Database::Handler& _db;
|
Database::Handler& _db;
|
||||||
const std::string _table;
|
const std::string _table;
|
||||||
const std::string _field;
|
const std::string _field;
|
||||||
|
|||||||
@@ -44,6 +44,20 @@ _db(db)
|
|||||||
setLayoutSizeAware(true);
|
setLayoutSizeAware(true);
|
||||||
|
|
||||||
_queryModel.setBatchSize(100);
|
_queryModel.setBatchSize(100);
|
||||||
|
|
||||||
|
// If an item is double clicked, select and emit signal
|
||||||
|
this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt)
|
||||||
|
{
|
||||||
|
if (!idx.isValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Wt::WModelIndexSet indexSet;
|
||||||
|
indexSet.insert(idx);
|
||||||
|
|
||||||
|
this->setSelectedIndexes( indexSet );
|
||||||
|
_sigDoubleClicked.emit( );
|
||||||
|
}, std::placeholders::_1, std::placeholders::_2));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -43,8 +43,14 @@ class TableFilterGenre : public Wt::WTableView, public Filter
|
|||||||
|
|
||||||
void layoutSizeChanged (int width, int height);
|
void layoutSizeChanged (int width, int height);
|
||||||
|
|
||||||
|
typedef Wt::Signal<void> SigDoubleClicked;
|
||||||
|
|
||||||
|
SigDoubleClicked& sigDoubleClicked() { return _sigDoubleClicked; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
SigDoubleClicked _sigDoubleClicked;
|
||||||
|
|
||||||
Database::Handler& _db;
|
Database::Handler& _db;
|
||||||
|
|
||||||
// genre_name, count
|
// genre_name, count
|
||||||
|
|||||||
@@ -149,6 +149,27 @@ TrackView::getSelectedTracks(std::vector<Database::Track::id_type>& track_ids)
|
|||||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all selected tracks DONE...";
|
LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all selected tracks DONE...";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::size_t
|
||||||
|
TrackView::getNbSelectedTracks(void)
|
||||||
|
{
|
||||||
|
return this->selectedIndexes().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
TrackView::getFirstSelectedTrackPosition(void)
|
||||||
|
{
|
||||||
|
Wt::WModelIndexSet indexSet = this->selectedIndexes();
|
||||||
|
BOOST_FOREACH(Wt::WModelIndex index, indexSet)
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
return index.row();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
TrackView::getTracks(std::vector<Database::Track::id_type>& trackIds)
|
TrackView::getTracks(std::vector<Database::Track::id_type>& trackIds)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -43,9 +43,13 @@ class TrackView : public Wt::WTableView, public Filter
|
|||||||
// Create constraints for child filters (N/A)
|
// Create constraints for child filters (N/A)
|
||||||
void getConstraint(Constraint& constraint) {}
|
void getConstraint(Constraint& constraint) {}
|
||||||
|
|
||||||
|
|
||||||
// Get all the tracks that are currently selected
|
// Get all the tracks that are currently selected
|
||||||
void getSelectedTracks(std::vector<Database::Track::id_type>& track_ids);
|
void getSelectedTracks(std::vector<Database::Track::id_type>& trackIds);
|
||||||
|
|
||||||
|
std::size_t getNbSelectedTracks(void);
|
||||||
|
|
||||||
|
// Get the first position of the selected tracks (0 if nothing selected)
|
||||||
|
int getFirstSelectedTrackPosition(void);
|
||||||
|
|
||||||
// Get all the tracks
|
// Get all the tracks
|
||||||
void getTracks(std::vector<Database::Track::id_type>& track_ids);
|
void getTracks(std::vector<Database::Track::id_type>& track_ids);
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
.playqueue-playing {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user