[UI] Added playlist editing features
This commit is contained in:
@@ -20,7 +20,7 @@ ui = {
|
||||
enable = true;
|
||||
|
||||
resources = {
|
||||
docroot = "/usr/share/Wt/"
|
||||
docroot = "/var/lms/docroot"
|
||||
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);
|
||||
setTheme(bootstrapTheme);
|
||||
|
||||
useStyleSheet("css/lms.css");
|
||||
|
||||
// Add a resource bundle
|
||||
messageResourceBundle().use(appRoot() + "templates");
|
||||
|
||||
|
||||
+120
-66
@@ -24,14 +24,13 @@
|
||||
#include <Wt/WGridLayout>
|
||||
#include <Wt/WComboBox>
|
||||
#include <Wt/WPushButton>
|
||||
#include <Wt/WPopupMenu>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "TableFilter.hpp"
|
||||
#include "TableFilterGenre.hpp"
|
||||
#include "KeywordSearchFilter.hpp"
|
||||
#include "TrackView.hpp"
|
||||
#include "PlayQueue.hpp"
|
||||
|
||||
#include "Audio.hpp"
|
||||
|
||||
@@ -40,41 +39,37 @@ namespace UserInterface {
|
||||
Audio::Audio(SessionData& sessionData, Wt::WContainerWidget* parent)
|
||||
: Wt::WContainerWidget(parent),
|
||||
_db(sessionData.getDatabaseHandler()),
|
||||
_mediaPlayer(nullptr)
|
||||
_mediaPlayer(nullptr),
|
||||
_trackView(nullptr),
|
||||
_playQueue(nullptr)
|
||||
{
|
||||
|
||||
Wt::WGridLayout *mainLayout = new Wt::WGridLayout();
|
||||
this->setLayout(mainLayout);
|
||||
|
||||
// Filters
|
||||
Wt::WHBoxLayout *filterLayout = new Wt::WHBoxLayout();
|
||||
|
||||
{
|
||||
TableFilterGenre *filter = new TableFilterGenre(_db);
|
||||
filterLayout->addWidget(filter);
|
||||
_filterChain.addFilter(filter);
|
||||
}
|
||||
{
|
||||
TableFilter *filter = new TableFilter(_db, "track", "artist_name");
|
||||
filterLayout->addWidget(filter);
|
||||
_filterChain.addFilter(filter);
|
||||
}
|
||||
{
|
||||
TableFilter *filter = new TableFilter(_db, "track", "release_name");
|
||||
filterLayout->addWidget(filter);
|
||||
_filterChain.addFilter(filter);
|
||||
}
|
||||
TableFilterGenre *filterGenre = new TableFilterGenre(_db);
|
||||
filterLayout->addWidget(filterGenre);
|
||||
_filterChain.addFilter(filterGenre);
|
||||
|
||||
TableFilter *filterArtist = new TableFilter(_db, "track", "artist_name", "Artist");
|
||||
filterLayout->addWidget(filterArtist);
|
||||
_filterChain.addFilter(filterArtist);
|
||||
|
||||
TableFilter *filterRelease = new TableFilter(_db, "track", "release_name", "Release");
|
||||
filterLayout->addWidget(filterRelease);
|
||||
_filterChain.addFilter(filterRelease);
|
||||
|
||||
mainLayout->addLayout(filterLayout, 0, 0);
|
||||
// ENDOF(Filters)
|
||||
|
||||
// TODO ADD controls (Play, add)
|
||||
// TODO ADD some stats (nb files, total duration, etc.)
|
||||
|
||||
Wt::WVBoxLayout* trackLayout = new Wt::WVBoxLayout();
|
||||
|
||||
TrackView* trackView = new TrackView(_db);
|
||||
trackLayout->addWidget(trackView, 1);
|
||||
_trackView = new TrackView(_db);
|
||||
trackLayout->addWidget(_trackView, 1);
|
||||
|
||||
Wt::WHBoxLayout* trackControls = new Wt::WHBoxLayout();
|
||||
|
||||
@@ -90,9 +85,9 @@ _mediaPlayer(nullptr)
|
||||
|
||||
mainLayout->addLayout(trackLayout, 1, 0);
|
||||
|
||||
_filterChain.addFilter(trackView);
|
||||
_filterChain.addFilter(_trackView);
|
||||
|
||||
PlayQueue *playQueue = new PlayQueue(_db);
|
||||
_playQueue = new PlayQueue(_db);
|
||||
|
||||
// Playlist/PlayQueue
|
||||
{
|
||||
@@ -103,12 +98,49 @@ _mediaPlayer(nullptr)
|
||||
|
||||
Wt::WHBoxLayout* playlistControls = new Wt::WHBoxLayout();
|
||||
|
||||
playlistControls->addWidget(new Wt::WPushButton("Playlist"));
|
||||
playlistControls->addWidget(new Wt::WText("Duration:"));
|
||||
Wt::WPushButton *saveBtn = new Wt::WPushButton("Save");
|
||||
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->addWidget( playQueue, 1);
|
||||
playQueueLayout->addWidget( _playQueue, 1);
|
||||
|
||||
mainLayout->addLayout(playQueueLayout, 0, 1, 2, 1);
|
||||
}
|
||||
@@ -122,54 +154,27 @@ _mediaPlayer(nullptr)
|
||||
|
||||
// Double click on track
|
||||
// Set the selected tracks to the play queue
|
||||
trackView->trackDoubleClicked().connect(std::bind([=] ()
|
||||
{
|
||||
std::vector<Database::Track::id_type> trackIds;
|
||||
trackView->getSelectedTracks(trackIds);
|
||||
_trackView->trackDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddSelectedTracks));
|
||||
|
||||
playQueue->clear();
|
||||
playQueue->addTracks(trackIds);
|
||||
|
||||
playQueue->play();
|
||||
|
||||
}));
|
||||
// Double click on artist
|
||||
// Set the selected tracks to the play queue
|
||||
filterArtist->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks));
|
||||
filterRelease->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks));
|
||||
filterGenre->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks));
|
||||
|
||||
// Play button
|
||||
// Set the selected tracks to the play queue
|
||||
playBtn->clicked().connect(std::bind([=] ()
|
||||
{
|
||||
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();
|
||||
}));
|
||||
playBtn->clicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddSelectedTracks));
|
||||
|
||||
// Add Button
|
||||
// Add the selected tracks at the end of the play queue
|
||||
addBtn->clicked().connect(std::bind([=] ()
|
||||
{
|
||||
std::vector<Database::Track::id_type> trackIds;
|
||||
trackView->getSelectedTracks(trackIds);
|
||||
addBtn->clicked().connect(this, &Audio::addSelectedTracks);
|
||||
|
||||
// If nothing is selected, get the whole track list
|
||||
if (trackIds.empty())
|
||||
trackView->getTracks(trackIds);
|
||||
_playQueue->playTrack().connect(this, &Audio::playTrack);
|
||||
|
||||
playQueue->addTracks(trackIds);
|
||||
}));
|
||||
|
||||
playQueue->playTrack().connect(this, &Audio::playTrack);
|
||||
|
||||
_mediaPlayer->playbackEnded().connect(playQueue, &PlayQueue::handlePlaybackComplete);
|
||||
_mediaPlayer->playNext().connect(playQueue, &PlayQueue::playNext);
|
||||
_mediaPlayer->playPrevious().connect(playQueue, &PlayQueue::playPrevious);
|
||||
_mediaPlayer->playbackEnded().connect(_playQueue, &PlayQueue::handlePlaybackComplete);
|
||||
_mediaPlayer->playNext().connect(_playQueue, &PlayQueue::playNext);
|
||||
_mediaPlayer->playPrevious().connect(_playQueue, &PlayQueue::playPrevious);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -178,6 +183,55 @@ Audio::search(const std::string& 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
|
||||
Audio::playTrack(boost::filesystem::path p)
|
||||
{
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#include "common/SessionData.hpp"
|
||||
|
||||
#include "AudioMediaPlayer.hpp"
|
||||
#include "TrackView.hpp"
|
||||
#include "PlayQueue.hpp"
|
||||
|
||||
#include "FilterChain.hpp"
|
||||
|
||||
@@ -45,11 +47,22 @@ class Audio : public Wt::WContainerWidget
|
||||
|
||||
void playTrack(boost::filesystem::path p);
|
||||
|
||||
enum PlayQueueAddType
|
||||
{
|
||||
PlayQueueAddAllTracks,
|
||||
PlayQueueAddSelectedTracks,
|
||||
};
|
||||
|
||||
void playSelectedTracks(PlayQueueAddType addType);
|
||||
void addSelectedTracks();
|
||||
|
||||
void handlePlaylistSelected(Wt::WString name);
|
||||
|
||||
Database::Handler& _db;
|
||||
|
||||
AudioMediaPlayer* _mediaPlayer;
|
||||
TrackView* _trackView;
|
||||
PlayQueue* _playQueue;
|
||||
|
||||
FilterChain _filterChain;
|
||||
|
||||
|
||||
@@ -63,11 +63,13 @@ AudioMediaPlayer::AudioMediaPlayer( Wt::WContainerWidget *parent)
|
||||
|
||||
nextBtn->clicked().connect(std::bind([=] ()
|
||||
{
|
||||
_mediaPlayer->stop();
|
||||
_playNext.emit();
|
||||
}));
|
||||
|
||||
prevBtn->clicked().connect(std::bind([=] ()
|
||||
{
|
||||
_mediaPlayer->stop();
|
||||
_playPrevious.emit();
|
||||
}));
|
||||
}
|
||||
|
||||
+186
-10
@@ -17,15 +17,64 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Wt/WItemDelegate>
|
||||
|
||||
#include "logger/Logger.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 {
|
||||
|
||||
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)
|
||||
: Wt::WTableView(parent),
|
||||
_db(db),
|
||||
_playedId(-1)
|
||||
_playedId(trackIdInvalid)
|
||||
{
|
||||
_model = new Wt::WStandardItemModel(0, 4, this);
|
||||
|
||||
@@ -45,6 +94,9 @@ _playedId(-1)
|
||||
|
||||
this->setColumnHidden(0, true);
|
||||
|
||||
_itemDelegate = new PlayQueueItemDelegate();
|
||||
this->setItemDelegate(_itemDelegate);
|
||||
|
||||
this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt)
|
||||
{
|
||||
if (!idx.isValid())
|
||||
@@ -64,9 +116,9 @@ _playedId(-1)
|
||||
}
|
||||
|
||||
void
|
||||
PlayQueue::play(void)
|
||||
PlayQueue::play(int trackId)
|
||||
{
|
||||
readTrack(0);
|
||||
readTrack(trackId);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -99,23 +151,21 @@ PlayQueue::clear(void)
|
||||
_model->removeRows(0, _model->rowCount());
|
||||
|
||||
// Reset play id
|
||||
_playedId = -1;
|
||||
_playedId = trackIdInvalid;
|
||||
}
|
||||
|
||||
void
|
||||
PlayQueue::handlePlaybackComplete(void)
|
||||
{
|
||||
// Play the next track
|
||||
if (_playedId != -1)
|
||||
{
|
||||
if (_playedId != trackIdInvalid)
|
||||
readTrack(_playedId + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PlayQueue::playNext(void)
|
||||
{
|
||||
readTrack( _playedId != -1 ? _playedId + 1 : 0);
|
||||
readTrack( _playedId != trackIdInvalid ? _playedId + 1 : 0);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -136,15 +186,141 @@ PlayQueue::readTrack(int rowId)
|
||||
Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId);
|
||||
if (track)
|
||||
{
|
||||
_playedId = rowId;
|
||||
setPlayingTrackId(rowId);
|
||||
|
||||
_sigTrackPlay.emit(track->getPath());
|
||||
break;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
++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
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
class PlayQueueItemDelegate;
|
||||
|
||||
class PlayQueue : public Wt::WTableView
|
||||
{
|
||||
public:
|
||||
@@ -37,10 +39,16 @@ class PlayQueue : public Wt::WTableView
|
||||
|
||||
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 playPrevious(void); // Play the previous track
|
||||
|
||||
// List manipulations
|
||||
void delSelected(void);
|
||||
void moveSelectedUp(void);
|
||||
void moveSelectedDown(void);
|
||||
|
||||
// Signals
|
||||
Wt::Signal< boost::filesystem::path >& playTrack() { return _sigTrackPlay; }
|
||||
|
||||
@@ -52,6 +60,8 @@ class PlayQueue : public Wt::WTableView
|
||||
private:
|
||||
|
||||
void readTrack(int rowId);
|
||||
void setPlayingTrackId(int newRowId);
|
||||
void renumber(int firstId, int lastId);
|
||||
|
||||
Wt::Signal< boost::filesystem::path > _sigTrackPlay;
|
||||
|
||||
@@ -59,6 +69,8 @@ class PlayQueue : public Wt::WTableView
|
||||
|
||||
Wt::WStandardItemModel* _model;
|
||||
|
||||
PlayQueueItemDelegate* _itemDelegate;
|
||||
|
||||
int _playedId;
|
||||
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
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 ),
|
||||
Filter(),
|
||||
_db(db),
|
||||
@@ -33,8 +33,8 @@ _table(table),
|
||||
_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.addColumn( "track." + _field, field);
|
||||
_queryModel.addColumn( "COUNT(DISTINCT track.id)", "tracks");
|
||||
_queryModel.addColumn( "track." + _field, displayName);
|
||||
_queryModel.addColumn( "COUNT(DISTINCT track.id)", "Tracks");
|
||||
|
||||
this->setSelectionMode(Wt::ExtendedSelection);
|
||||
this->setSortingEnabled(false);
|
||||
@@ -46,6 +46,21 @@ _field(field)
|
||||
setLayoutSizeAware(true);
|
||||
|
||||
_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
|
||||
|
||||
@@ -33,7 +33,7 @@ class TableFilter : public Wt::WTableView, public Filter
|
||||
{
|
||||
|
||||
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
|
||||
void refresh(const Constraint& constraint);
|
||||
@@ -43,8 +43,14 @@ class TableFilter : public Wt::WTableView, public Filter
|
||||
|
||||
void layoutSizeChanged (int width, int height);
|
||||
|
||||
typedef Wt::Signal<void> SigDoubleClicked;
|
||||
|
||||
SigDoubleClicked& sigDoubleClicked() { return _sigDoubleClicked; }
|
||||
|
||||
protected:
|
||||
|
||||
SigDoubleClicked _sigDoubleClicked;
|
||||
|
||||
Database::Handler& _db;
|
||||
const std::string _table;
|
||||
const std::string _field;
|
||||
|
||||
@@ -44,6 +44,20 @@ _db(db)
|
||||
setLayoutSizeAware(true);
|
||||
|
||||
_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
|
||||
|
||||
@@ -43,8 +43,14 @@ class TableFilterGenre : public Wt::WTableView, public Filter
|
||||
|
||||
void layoutSizeChanged (int width, int height);
|
||||
|
||||
typedef Wt::Signal<void> SigDoubleClicked;
|
||||
|
||||
SigDoubleClicked& sigDoubleClicked() { return _sigDoubleClicked; }
|
||||
|
||||
protected:
|
||||
|
||||
SigDoubleClicked _sigDoubleClicked;
|
||||
|
||||
Database::Handler& _db;
|
||||
|
||||
// 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...";
|
||||
}
|
||||
|
||||
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
|
||||
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)
|
||||
void getConstraint(Constraint& constraint) {}
|
||||
|
||||
|
||||
// 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
|
||||
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