WIP. new layout on the audio view

This commit is contained in:
emeric
2014-10-17 00:11:53 +02:00
parent 27ea810f9e
commit 49d2475422
19 changed files with 412 additions and 477 deletions
+47 -47
View File
@@ -1,64 +1,64 @@
bin_PROGRAMS = lms
lms_SOURCES = \
$(srcdir)/main/main.cpp \
$(srcdir)/av/CodecContext.cpp \
$(srcdir)/av/Common.cpp \
$(srcdir)/av/Dictionary.cpp \
$(srcdir)/av/FormatContext.cpp \
$(srcdir)/av/InputFormatContext.cpp \
$(srcdir)/av/Stream.cpp \
$(srcdir)/config/ConfigReader.cpp \
$(srcdir)/cover/CoverArt.cpp \
$(srcdir)/cover/CoverArtGrabber.cpp \
$(srcdir)/database/Artist.cpp \
$(srcdir)/database/Genre.cpp \
$(srcdir)/database/Release.cpp \
$(srcdir)/database/MediaDirectory.cpp \
$(srcdir)/database/Track.cpp \
$(srcdir)/database/DatabaseHandler.cpp \
$(srcdir)/database/SqlQuery.cpp \
$(srcdir)/database/Video.cpp \
$(srcdir)/database/User.cpp \
$(srcdir)/database-updater/DatabaseUpdater.cpp \
$(srcdir)/database-updater/Checksum.cpp \
$(srcdir)/logger/Logger.cpp \
$(srcdir)/metadata/AvFormat.cpp \
$(srcdir)/metadata/Utils.cpp \
$(srcdir)/remote/server/Connection.cpp \
$(srcdir)/remote/server/ConnectionManager.cpp \
$(srcdir)/main/main.cpp \
$(srcdir)/av/CodecContext.cpp \
$(srcdir)/av/Common.cpp \
$(srcdir)/av/Dictionary.cpp \
$(srcdir)/av/FormatContext.cpp \
$(srcdir)/av/InputFormatContext.cpp \
$(srcdir)/av/Stream.cpp \
$(srcdir)/config/ConfigReader.cpp \
$(srcdir)/cover/CoverArt.cpp \
$(srcdir)/cover/CoverArtGrabber.cpp \
$(srcdir)/database/Artist.cpp \
$(srcdir)/database/Genre.cpp \
$(srcdir)/database/Release.cpp \
$(srcdir)/database/MediaDirectory.cpp \
$(srcdir)/database/Track.cpp \
$(srcdir)/database/DatabaseHandler.cpp \
$(srcdir)/database/SqlQuery.cpp \
$(srcdir)/database/Video.cpp \
$(srcdir)/database/User.cpp \
$(srcdir)/database-updater/DatabaseUpdater.cpp \
$(srcdir)/database-updater/Checksum.cpp \
$(srcdir)/logger/Logger.cpp \
$(srcdir)/metadata/AvFormat.cpp \
$(srcdir)/metadata/Utils.cpp \
$(srcdir)/remote/server/Connection.cpp \
$(srcdir)/remote/server/ConnectionManager.cpp \
$(srcdir)/remote/server/AudioCollectionRequestHandler.cpp \
$(srcdir)/remote/server/AuthRequestHandler.cpp \
$(srcdir)/remote/server/MediaRequestHandler.cpp \
$(srcdir)/remote/server/AuthRequestHandler.cpp \
$(srcdir)/remote/server/MediaRequestHandler.cpp \
$(srcdir)/remote/server/RequestHandler.cpp \
$(srcdir)/remote/server/Server.cpp \
$(srcdir)/service/ServiceManager.cpp \
$(srcdir)/service/DatabaseUpdateService.cpp \
$(srcdir)/remote/server/Server.cpp \
$(srcdir)/service/ServiceManager.cpp \
$(srcdir)/service/DatabaseUpdateService.cpp \
$(srcdir)/service/UserInterfaceService.cpp \
$(srcdir)/service/RemoteServerService.cpp \
$(srcdir)/transcode/AvConvTranscoder.cpp \
$(srcdir)/transcode/Format.cpp \
$(srcdir)/transcode/Parameters.cpp \
$(srcdir)/transcode/InputMediaFile.cpp \
$(srcdir)/ui/LmsApplication.cpp \
$(srcdir)/ui/LmsHome.cpp \
$(srcdir)/ui/auth/LmsAuth.cpp \
$(srcdir)/service/RemoteServerService.cpp \
$(srcdir)/transcode/AvConvTranscoder.cpp \
$(srcdir)/transcode/Format.cpp \
$(srcdir)/transcode/Parameters.cpp \
$(srcdir)/transcode/InputMediaFile.cpp \
$(srcdir)/ui/LmsApplication.cpp \
$(srcdir)/ui/LmsHome.cpp \
$(srcdir)/ui/auth/LmsAuth.cpp \
$(srcdir)/ui/audio/AudioWidget.cpp \
$(srcdir)/ui/audio/AudioDatabaseWidget.cpp \
$(srcdir)/ui/audio/AudioMediaPlayerWidget.cpp \
$(srcdir)/ui/audio/SearchFilterWidget.cpp \
$(srcdir)/ui/audio/TableFilterWidget.cpp \
$(srcdir)/ui/audio/TrackWidget.cpp \
$(srcdir)/ui/audio/AudioMediaPlayerWidget.cpp \
$(srcdir)/ui/audio/FilterChain.cpp \
$(srcdir)/ui/audio/KeywordSearchFilter.cpp \
$(srcdir)/ui/audio/TableFilter.cpp \
$(srcdir)/ui/audio/TrackView.cpp \
$(srcdir)/ui/common/DirectoryValidator.cpp \
$(srcdir)/ui/common/SessionData.cpp \
$(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \
$(srcdir)/ui/video/VideoWidget.cpp \
$(srcdir)/ui/video/VideoDatabaseWidget.cpp \
$(srcdir)/ui/video/VideoMediaPlayerWidget.cpp \
$(srcdir)/ui/video/VideoParametersDialog.cpp \
$(srcdir)/ui/video/VideoMediaPlayerWidget.cpp \
$(srcdir)/ui/video/VideoParametersDialog.cpp \
$(srcdir)/ui/settings/Settings.cpp \
$(srcdir)/ui/settings/SettingsAccountFormView.cpp \
$(srcdir)/ui/settings/SettingsAudioFormView.cpp \
$(srcdir)/ui/settings/SettingsAudioFormView.cpp \
$(srcdir)/ui/settings/SettingsDatabaseFormView.cpp \
$(srcdir)/ui/settings/SettingsFirstConnectionFormView.cpp \
$(srcdir)/ui/settings/SettingsMediaDirectories.cpp \
+3
View File
@@ -70,6 +70,7 @@ AuthRequestHandler::processPassword(const AuthRequest::Password& request, AuthRe
{
case Wt::Auth::PasswordInvalid:
response.set_type(AuthResponse::PasswordResult::TypePasswordInvalid);
LMS_LOG(MOD_REMOTE, SEV_NOTICE) << "User '" << request.user_login() << "': invalid password";
res = true;
break;
case Wt::Auth::LoginThrottling:
@@ -81,9 +82,11 @@ AuthRequestHandler::processPassword(const AuthRequest::Password& request, AuthRe
response.set_type(AuthResponse::PasswordResult::TypePasswordValid);
// Log the user in
_db.getLogin().login( user );
LMS_LOG(MOD_REMOTE, SEV_NOTICE) << "User '" << request.user_login() << "' successfully logged in";
res = true;
break;
default:
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Cannot handle password result!";
break;
}
+50 -47
View File
@@ -18,8 +18,24 @@
*/
#include <Wt/WBootstrapTheme>
#include <Wt/WVBoxLayout>
#include <Wt/WHBoxLayout>
#include <Wt/WNavigationBar>
#include <Wt/WStackedWidget>
#include <Wt/WMenu>
#include <Wt/WNavigationBar>
#include <Wt/WPopupMenu>
#include <Wt/WPopupMenuItem>
#include <Wt/WVBoxLayout>
#include <Wt/Auth/Identity>
#include "settings/Settings.hpp"
#include "auth/LmsAuth.hpp"
#include "logger/Logger.hpp"
#include "LmsHome.hpp"
#include "settings/SettingsFirstConnectionFormView.hpp"
@@ -64,65 +80,52 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, boost::filesystem::p
setTitle("LMS"); // application title
bool firstConnection;
{
Wt::Dbo::Transaction transaction(_sessionData.getDatabaseHandler().getSession());
// Create a Vertical layout: top is the nav bar, bottom is the contents
Wt::WVBoxLayout *layout = new Wt::WVBoxLayout(this->root());
// Create a navigation bar with a link to a web page.
Wt::WNavigationBar *navigation = new Wt::WNavigationBar();
navigation->setTitle("LMS");
navigation->setResponsive(true);
navigation->addStyleClass("main-nav");
firstConnection = (Database::User::getAll(_sessionData.getDatabaseHandler().getSession()).size() == 0);
}
Wt::WStackedWidget *contentsStack = new Wt::WStackedWidget();
// If here is no account in the database, launch the first connection wizard
if (firstConnection)
{
// Hack, use the auth widget builtin strings
builtinLocalizedStrings().useBuiltin(skeletons::AuthStrings_xml1);
// Setup a Left-aligned menu.
Wt::WMenu *leftMenu = new Wt::WMenu(contentsStack);
navigation->addMenu(leftMenu);
root()->addWidget( new Settings::FirstConnectionFormView(_sessionData));
}
else
{
_sessionData.getDatabaseHandler().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent);
_audioWidget = new AudioWidget(_sessionData);
_videoWidget = new VideoWidget(_sessionData);
LmsAuth *authWidget = new LmsAuth(_sessionData.getDatabaseHandler());
leftMenu->addItem("Audio", _audioWidget);
leftMenu->addItem("Video", _videoWidget);
// leftMenu->addItem("Settings", new Settings::Settings(_sessionData));
authWidget->model()->addPasswordAuth(&Database::Handler::getPasswordService());
authWidget->setRegistrationEnabled(false);
// Setup a Right-aligned menu.
Wt::WMenu *rightMenu = new Wt::WMenu();
authWidget->processEnvironment();
navigation->addMenu(rightMenu, Wt::AlignRight);
root()->addWidget(authWidget);
}
}
Wt::WPopupMenu *popup = new Wt::WPopupMenu();
popup->addItem("Logout");
// popup->itemSelected().connect(this, &LmsHome::handleUserMenuSelected);
/* Wt::WMenuItem *item = new Wt::WMenuItem( user.identity(Wt::Auth::Identity::LoginName) );
item->setMenu(popup);
rightMenu->addItem(item);*/
void
LmsApplication::handleAuthEvent(void)
{
if (_sessionData.getDatabaseHandler().getLogin().loggedIn())
{
if (_home == nullptr) {
_home = new LmsHome(_sessionData, root() );
}
else
std::cerr << "Already logged in??" << std::endl;
}
else
{
std::cerr << "user log out" << std::endl;
if (_home != nullptr) {
delete _home;
_home = nullptr;
// Add a Search control.
/* _searchEdit = new Wt::WLineEdit();
_searchEdit->setEmptyText("Search...");
// Hack: quit/redirect in order to avoid 'signal not exposed' problems
// TODO, investigate/remove?
quit();
redirect("/");
_searchEdit->enterPressed().connect(this, &LmsHome::handleSearch);
*/
// navigation->addSearch(_searchEdit, Wt::AlignLeft);
}
else
std::cerr << "Already logged out??" << std::endl;
}
layout->addWidget(navigation);
layout->addWidget(contentsStack, 1);
layout->setContentsMargins(0, 0, 0, 0);
}
} // namespace UserInterface
+2
View File
@@ -44,6 +44,8 @@ class LmsApplication : public Wt::WApplication
SessionData _sessionData;
LmsHome* _home;
AudioWidget* _audioWidget;
VideoWidget* _videoWidget;
};
+1 -41
View File
@@ -22,6 +22,7 @@
#include <Wt/WNavigationBar>
#include <Wt/WPopupMenu>
#include <Wt/WPopupMenuItem>
#include <Wt/WVBoxLayout>
#include <Wt/Auth/Identity>
#include "settings/Settings.hpp"
@@ -36,48 +37,7 @@ LmsHome::LmsHome(SessionData& sessionData, Wt::WContainerWidget* parent)
{
const Wt::Auth::User& user = sessionData.getDatabaseHandler().getLogin().user();
// Create a navigation bar with a link to a web page.
Wt::WNavigationBar *navigation = new Wt::WNavigationBar(this);
navigation->setTitle("LMS");
navigation->setResponsive(true);
navigation->addStyleClass("main-nav");
Wt::WStackedWidget *contentsStack = new Wt::WStackedWidget(this);
// Setup a Left-aligned menu.
Wt::WMenu *leftMenu = new Wt::WMenu(contentsStack);
navigation->addMenu(leftMenu);
_audioWidget = new AudioWidget(_sessionData);
_videoWidget = new VideoWidget(_sessionData);
leftMenu->addItem("Audio", _audioWidget);
leftMenu->addItem("Video", _videoWidget);
leftMenu->addItem("Settings", new Settings::Settings(_sessionData));
// Setup a Right-aligned menu.
Wt::WMenu *rightMenu = new Wt::WMenu();
navigation->addMenu(rightMenu, Wt::AlignRight);
Wt::WPopupMenu *popup = new Wt::WPopupMenu();
popup->addItem("Logout");
popup->itemSelected().connect(this, &LmsHome::handleUserMenuSelected);
Wt::WMenuItem *item = new Wt::WMenuItem( user.identity(Wt::Auth::Identity::LoginName) );
item->setMenu(popup);
rightMenu->addItem(item);
// Add a Search control.
_searchEdit = new Wt::WLineEdit();
_searchEdit->setEmptyText("Search...");
_searchEdit->enterPressed().connect(this, &LmsHome::handleSearch);
navigation->addSearch(_searchEdit, Wt::AlignLeft);
addWidget(contentsStack);
}
-126
View File
@@ -1,126 +0,0 @@
/*
* Copyright (C) 2013 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 <boost/foreach.hpp>
#include <Wt/WTable> // TODO
#include <Wt/WBreak> // TODO
#include "AudioDatabaseWidget.hpp"
#include "TableFilterWidget.hpp"
#include "SearchFilterWidget.hpp"
#include "TrackWidget.hpp"
namespace UserInterface {
AudioDatabaseWidget::AudioDatabaseWidget( Database::Handler& db, Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent),
_refreshingFilters(false)
{
std::size_t idFilter (0);
{
SearchFilterWidget* search = new SearchFilterWidget(this);
_filters.push_back( search );
search->update().connect( boost::bind(&AudioDatabaseWidget::handleFilterUpdated, this, idFilter++) );
}
Wt::WTable* table = new Wt::WTable(this);
{
TableFilterWidget* filterTable = new TableFilterWidget(db, "genre", "name", table->elementAt(0,0));
_filters.push_back( filterTable );
filterTable->update().connect( boost::bind(&AudioDatabaseWidget::handleFilterUpdated, this, idFilter++) );
}
{
TableFilterWidget* filterTable = new TableFilterWidget(db, "artist", "name", table->elementAt(0,1));
_filters.push_back( filterTable );
filterTable->update().connect( boost::bind(&AudioDatabaseWidget::handleFilterUpdated, this, idFilter++) );
}
{
TableFilterWidget* filterTable = new TableFilterWidget(db, "release", "name", table->elementAt(0,2));
_filters.push_back( filterTable );
filterTable->update().connect( boost::bind(&AudioDatabaseWidget::handleFilterUpdated, this, idFilter++) );
}
{
TrackWidget* track = new TrackWidget(db, this);
_filters.push_back( track );
track->trackSelected().connect(this, &AudioDatabaseWidget::handleTrackSelected);
}
}
void
AudioDatabaseWidget::search(const std::string& text)
{
SearchFilterWidget* searchWidget ( dynamic_cast<SearchFilterWidget*>(_filters.front() ) );
searchWidget->setText(text);
}
void
AudioDatabaseWidget::handleTrackSelected(boost::filesystem::path p)
{
_trackSelected.emit(p);
}
void
AudioDatabaseWidget::handleFilterUpdated(std::size_t idFilterUpdated)
{
// TODO disconnect from event!
if (_refreshingFilters)
return;
_refreshingFilters = true;
FilterWidget::Constraint currentConstraint;
currentConstraint.where.And( WhereClause( "track.artist_id = artist.id and track.release_id = release.id and track_genre.track_id = track.id and genre.id = track_genre.genre_id"));
for (std::size_t idFilter = 0; idFilter < _filters.size(); ++idFilter)
{
FilterWidget* filter = _filters.at(idFilter);
// Apply contraints created by previous filters
if (idFilter > idFilterUpdated) {
filter->refresh(currentConstraint);
}
// Get constraints generated by this filter
// (Note: adding accross successive calls)
filter->getConstraint(currentConstraint);
}
_refreshingFilters = false;
}
void
AudioDatabaseWidget::selectNextTrack(void)
{
TrackWidget* trackWidget ( dynamic_cast<TrackWidget*>(_filters.back() ) );
trackWidget->selectNextTrack();
}
} // namespace UserInterface
-62
View File
@@ -1,62 +0,0 @@
/*
* Copyright (C) 2013 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/>.
*/
#ifndef AUDIO_DB_WIDGET_HPP
#define AUDIO_DB_WIDGET_HPP
#include <boost/filesystem/path.hpp>
#include <Wt/WContainerWidget>
#include <Wt/WSignal>
#include "common/SessionData.hpp"
#include "FilterWidget.hpp"
namespace UserInterface {
class AudioDatabaseWidget : public Wt::WContainerWidget
{
public:
AudioDatabaseWidget( Database::Handler& db, Wt::WContainerWidget *parent = 0);
void search(const std::string& text);
void selectNextTrack(void); // Will later emit the next selected track
// Signals
Wt::Signal< boost::filesystem::path >& trackSelected() { return _trackSelected; }
private:
Wt::Signal< boost::filesystem::path > _trackSelected;
void handleTrackSelected(boost::filesystem::path p);
void handleFilterUpdated(std::size_t idFilter);
std::vector<FilterWidget*> _filters; // Free Search, Genre, Artist, Release, etc.
bool _refreshingFilters;
};
} // namespace UserInterface
#endif
+76 -41
View File
@@ -17,10 +17,20 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WBreak>
#include <boost/bind.hpp>
#include <Wt/WVBoxLayout>
#include <Wt/WHBoxLayout>
#include <Wt/WGridLayout>
#include <Wt/WComboBox>
#include <Wt/WPushButton>
#include "logger/Logger.hpp"
#include "TableFilter.hpp"
#include "KeywordSearchFilter.hpp"
#include "TrackView.hpp"
#include "AudioWidget.hpp"
namespace UserInterface {
@@ -28,33 +38,80 @@ namespace UserInterface {
AudioWidget::AudioWidget(SessionData& sessionData, Wt::WContainerWidget* parent)
: Wt::WContainerWidget(parent),
_db(sessionData.getDatabaseHandler()),
_audioDbWidget(nullptr),
_mediaPlayer(nullptr),
_imgResource(nullptr),
_img(nullptr)
_mediaPlayer(nullptr)
{
_audioDbWidget = new AudioDatabaseWidget(sessionData.getDatabaseHandler(), this);
_audioDbWidget->trackSelected().connect(this, &AudioWidget::playTrack);
Wt::WGridLayout *mainLayout = new Wt::WGridLayout();
this->setLayout(mainLayout);
// Filters
Wt::WHBoxLayout *filterLayout = new Wt::WHBoxLayout();
{
TableFilter *filter = new TableFilter(_db, "genre", "name");
filterLayout->addWidget(filter);
_filterChain.addFilter(filter);
}
{
TableFilter *filter = new TableFilter(_db, "artist", "name");
filterLayout->addWidget(filter);
_filterChain.addFilter(filter);
}
{
TableFilter *filter = new TableFilter(_db, "release", "name");
filterLayout->addWidget(filter);
_filterChain.addFilter(filter);
}
mainLayout->addLayout(filterLayout, 0, 0);
// ENDOF(Filters)
TrackView* trackView = new TrackView(_db);
mainLayout->addWidget( trackView, 1, 0);
_filterChain.addFilter(trackView);
// TODO Playlist here
{
Wt::WVBoxLayout* playlist = new Wt::WVBoxLayout();
Wt::WHBoxLayout* playlistControls = new Wt::WHBoxLayout();
Wt::WComboBox *cb = new Wt::WComboBox();
cb->addItem("metal");
cb->addItem("rock");
cb->addItem("top50");
cb->setWidth(100);
playlistControls->addWidget(cb, 1);
playlistControls->addWidget(new Wt::WPushButton("Rename"));
playlistControls->addWidget(new Wt::WPushButton("+"));
playlistControls->addWidget(new Wt::WPushButton("-"));
playlist->addLayout(playlistControls);
playlist->addWidget( new TableFilter(_db, "release", "name"), 1);
mainLayout->addLayout(playlist, 0, 1, 2, 1);
}
_mediaPlayer = new AudioMediaPlayerWidget();
mainLayout->addWidget(_mediaPlayer, 2, 0, 1, 2);
mainLayout->setColumnStretch(0, 1);
mainLayout->setRowStretch(1, 1);
mainLayout->setRowResizable(0, true, Wt::WLength(200, Wt::WLength::Pixel));
mainLayout->setColumnResizable(0, true);
_mediaPlayer = new AudioMediaPlayerWidget(this);
_mediaPlayer->playbackEnded().connect(this, &AudioWidget::handleTrackEnded);
this->addWidget(new Wt::WBreak());
// Image
_imgResource = new Wt::WMemoryResource(this);
_imgLink.setResource( _imgResource);
_img = new Wt::WImage(_imgLink, this);
trackView->trackSelected().connect(boost::bind(&AudioWidget::playTrack, this, _1));
}
void
AudioWidget::search(const std::string& searchText)
{
_audioDbWidget->search(searchText);
_filterChain.searchKeyword(searchText);
}
void
AudioWidget::playTrack(boost::filesystem::path p)
{
@@ -71,7 +128,7 @@ AudioWidget::playTrack(boost::filesystem::path p)
bitrate = user->getAudioBitrate();
else
{
LMS_LOG(MOD_UI, SEV_ERROR) << "Can't play video: user does not exists!";
LMS_LOG(MOD_UI, SEV_ERROR) << "Can't play: user does not exists!";
return; // TODO logout?
}
}
@@ -83,27 +140,6 @@ AudioWidget::playTrack(boost::filesystem::path p)
parameters.setBitrate(Transcode::Stream::Audio, bitrate);
_mediaPlayer->load( parameters );
// Refresh cover
{
std::vector<CoverArt::CoverArt> covers = inputFile.getCovers();
if (!covers.empty())
{
LMS_LOG(MOD_UI, SEV_DEBUG) << "Cover found!";
if (!covers.front().scale(256)) // TODO
LMS_LOG(MOD_UI, SEV_ERROR) << "Cannot resize!";
//_imgResource->setMimeType(covers.front().getMimeType());
_imgResource->setData(covers.front().getData());
}
else {
LMS_LOG(MOD_UI, SEV_DEBUG) << "No cover found!";
_imgResource->setData( std::vector<unsigned char>());
}
_imgResource->setChanged();
}
}
catch( std::exception &e)
{
@@ -111,11 +147,10 @@ AudioWidget::playTrack(boost::filesystem::path p)
}
}
void
void
AudioWidget::handleTrackEnded(void)
{
LMS_LOG(MOD_UI, SEV_DEBUG) << "Track playback ended!";
_audioDbWidget->selectNextTrack();
}
} // namespace UserInterface
+6 -13
View File
@@ -22,13 +22,13 @@
#include <string>
#include <Wt/WLink>
#include <Wt/WImage>
#include <Wt/WContainerWidget>
#include <Wt/WMemoryResource>
#include "audio/AudioMediaPlayerWidget.hpp"
#include "audio/AudioDatabaseWidget.hpp"
#include "common/SessionData.hpp"
#include "AudioMediaPlayerWidget.hpp"
#include "FilterChain.hpp"
namespace UserInterface {
@@ -49,16 +49,9 @@ class AudioWidget : public Wt::WContainerWidget
Database::Handler& _db;
AudioDatabaseWidget* _audioDbWidget;
AudioMediaPlayerWidget* _mediaPlayer;
// Image
Wt::WMemoryResource *_imgResource;
Wt::WLink _imgLink;
Wt::WImage *_img;
FilterChain _filterChain;
};
@@ -17,35 +17,30 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FILTER_WIDGET_HPP
#define FILTER_WIDGET_HPP
#include <boost/foreach.hpp>
#include <string>
#include <list>
#ifndef FILTER_HPP
#define FILTER_HPP
#include <Wt/WSignal>
#include <Wt/WContainerWidget>
#include "database/SqlQuery.hpp"
namespace UserInterface {
class FilterWidget : public Wt::WContainerWidget {
class Filter
{
public:
struct Constraint {
WhereClause where; // WHERE SQL clause
};
FilterWidget(Wt::WContainerWidget* parent = 0) : Wt::WContainerWidget(parent) {}
virtual ~FilterWidget() {}
Filter() {}
virtual ~Filter() {}
// Refresh filter Widget using constraints created by parent filters
// Refresh filter using constraints created by parent filters
virtual void refresh(const Constraint& constraint) = 0;
// Update constraints for child filters
// Update constraints for next filters
virtual void getConstraint(Constraint& constraint) = 0;
// Emitted when a constraint has changed
+76
View File
@@ -0,0 +1,76 @@
/*
* 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 "FilterChain.hpp"
namespace UserInterface {
FilterChain::FilterChain()
: _refreshingFilters(false)
{
addFilter(&_keywordSearchFilter);
}
void
FilterChain::addFilter(Filter* filter)
{
filter->update().connect(boost::bind(&FilterChain::updateFilters, this, _filters.size()));
_filters.push_back(filter);
}
void
FilterChain::searchKeyword(const std::string& text)
{
_keywordSearchFilter.setText(text);
}
void
FilterChain::updateFilters(std::size_t startIdx)
{
// Prevent loops
if (_refreshingFilters)
return;
_refreshingFilters = true;
Filter::Constraint currentConstraint;
// TODO: replace with inner joins?
currentConstraint.where.And( WhereClause( "track.artist_id = artist.id and track.release_id = release.id and track_genre.track_id = track.id and genre.id = track_genre.genre_id"));
for (std::size_t idFilter = 0; idFilter < _filters.size(); ++idFilter)
{
Filter* filter = _filters.at(idFilter);
// Apply contraints created by previous filters
if (idFilter > startIdx) {
filter->refresh(currentConstraint);
}
// Get constraints generated by this filter
// (Note: adding accross successive calls)
filter->getConstraint(currentConstraint);
}
_refreshingFilters = false;
}
} // namespace UserInterface
+57
View File
@@ -0,0 +1,57 @@
/*
* 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/>.
*/
#ifndef FILTER_CHAIN_HPP
#define FILTER_CHAIN_HPP
#include <Wt/WSignal>
#include "Filter.hpp"
#include "KeywordSearchFilter.hpp"
namespace UserInterface {
// FilterChain
class FilterChain
{
public:
FilterChain();
void addFilter(Filter* filter);
// First filter is a keywork search
void searchKeyword(const std::string& text);
// Update filters from filter @ startIdx
void updateFilters(std::size_t startIdx);
private:
KeywordSearchFilter _keywordSearchFilter;
// No ownership
std::vector<Filter*> _filters;
bool _refreshingFilters;
};
} // namespace UserInterface
#endif
@@ -17,19 +17,16 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WLabel>
#include "SearchFilterWidget.hpp"
#include "KeywordSearchFilter.hpp"
namespace UserInterface {
SearchFilterWidget::SearchFilterWidget(Wt::WContainerWidget* parent)
: FilterWidget( parent )
KeywordSearchFilter::KeywordSearchFilter()
{
}
void
SearchFilterWidget::setText(const std::string& text)
KeywordSearchFilter::setText(const std::string& text)
{
_lastEmittedText = text;
emitUpdate();
@@ -37,7 +34,7 @@ SearchFilterWidget::setText(const std::string& text)
// Get constraints created by this filter
void
SearchFilterWidget::getConstraint(Constraint& constraint)
KeywordSearchFilter::getConstraint(Constraint& constraint)
{
// No active search means no constaint!
if (!_lastEmittedText.empty()) {
@@ -17,18 +17,19 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SEARCH_FILTER_WIDGET_PP
#define SEARCH_FILTER_WIDGET_PP
#ifndef KEYWORD_SEARCH_FILTER_HPP
#define KEYWORD_SEARCH_FILTER_HPP
#include <Wt/WLineEdit>
#include <string>
#include "FilterWidget.hpp"
#include "Filter.hpp"
namespace UserInterface {
class SearchFilterWidget : public FilterWidget {
class KeywordSearchFilter : public Filter
{
public:
SearchFilterWidget(Wt::WContainerWidget* parent = 0);
KeywordSearchFilter();
void setText(const std::string& text);
@@ -21,36 +21,49 @@
#include "logger/Logger.hpp"
#include "TableFilterWidget.hpp"
#include "TableFilter.hpp"
namespace UserInterface {
TableFilterWidget::TableFilterWidget(Database::Handler& db, std::string table, std::string field, Wt::WContainerWidget* parent)
: FilterWidget( parent ),
TableFilter::TableFilter(Database::Handler& db, std::string table, std::string field, Wt::WContainerWidget* parent)
: Wt::WTableView( parent ),
Filter(),
_db(db),
_table(table),
_field(field),
_tableView(nullptr)
_field(field)
{
_queryModel.setQuery( _db.getSession().query< ResultType >("select " + _table + "." + _field + ",count(DISTINCT track.id),0 as ORDERBY from track,artist,release,genre,track_genre WHERE track.artist_id = artist.id and track.release_id = release.id and track_genre.track_id = track.id and genre.id = track_genre.genre_id GROUP BY " + _table + "." + _field + " UNION select '<All>',0,1 AS ORDERBY").orderBy("ORDERBY DESC," + _table + "." + _field));
_queryModel.addColumn( _table + "." + _field, table);
_queryModel.addColumn( "count(DISTINCT track.id)", "Tracks");
_tableView = new Wt::WTableView( this );
_tableView->resize(250, 200); // TODO
_tableView->setSelectionMode(Wt::ExtendedSelection);
_tableView->setSortingEnabled(false);
_tableView->setAlternatingRowColors(true);
_tableView->setModel(&_queryModel);
this->setSelectionMode(Wt::ExtendedSelection);
this->setSortingEnabled(false);
this->setAlternatingRowColors(true);
this->setModel(&_queryModel);
_tableView->selectionChanged().connect(this, &TableFilterWidget::emitUpdate);
this->selectionChanged().connect(this, &TableFilter::emitUpdate);
setLayoutSizeAware(true);
_queryModel.setBatchSize(100);
}
void
TableFilter::layoutSizeChanged (int width, int height)
{
LMS_LOG(MOD_UI, SEV_DEBUG) << "LAYOUT CHANGED!";
/* TODO
std::size_t trackColumnSize = this->columnWidth(1).toPixels() + 30 ;
// Set the remaining size for the name column
this->setColumnWidth(0, width - 7 - trackColumnSize);
*/
}
// Set constraints on this filter
void
TableFilterWidget::refresh(const Constraint& constraint)
TableFilter::refresh(const Constraint& constraint)
{
SqlQuery sqlQuery;
@@ -73,16 +86,16 @@ TableFilterWidget::refresh(const Constraint& constraint)
query.bind(bindArg);
}
_queryModel.setQuery( query, true /* Keep columns */);
_queryModel.setQuery( query, true );
LMS_LOG(MOD_UI, SEV_DEBUG) << "Finish !";
}
// Get constraint created by this filter
void
TableFilterWidget::getConstraint(Constraint& constraint)
TableFilter::getConstraint(Constraint& constraint)
{
Wt::WModelIndexSet indexSet = _tableView->selectedIndexes();
Wt::WModelIndexSet indexSet = this->selectedIndexes();
// WHERE statement
WhereClause clause;
@@ -17,29 +17,31 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TABLE_VIEW_FILTER_WIDGET_HPP
#define TABLE_VIEW_FILTER_WIDGET_HPP
#ifndef TABLE_FILTER_HPP
#define TABLE_FILTER_HPP
#include <Wt/Dbo/QueryModel>
#include <Wt/WTableView>
#include "FilterWidget.hpp"
#include "Filter.hpp"
#include "database/DatabaseHandler.hpp"
namespace UserInterface {
class TableFilterWidget : public FilterWidget
class TableFilter : public Wt::WTableView, public Filter
{
public:
TableFilterWidget(Database::Handler& db, std::string table, std::string field, Wt::WContainerWidget* parent = 0);
TableFilter(Database::Handler& db, std::string table, std::string field, Wt::WContainerWidget* parent = 0);
// Set constraints on this filter
virtual void refresh(const Constraint& constraint);
void refresh(const Constraint& constraint);
// Get constraints created by this filter
virtual void getConstraint(Constraint& constraint);
void getConstraint(Constraint& constraint);
void layoutSizeChanged (int width, int height);
protected:
@@ -51,7 +53,6 @@ class TableFilterWidget : public FilterWidget
typedef boost::tuple<std::string, int, int> ResultType;
Wt::Dbo::QueryModel< ResultType > _queryModel;
Wt::WTableView* _tableView;
};
} // namespace UserInterface
@@ -25,18 +25,14 @@
#include "logger/Logger.hpp"
#include "TrackWidget.hpp"
#include "TrackView.hpp"
namespace UserInterface {
TrackWidget::TrackWidget( Database::Handler& db, Wt::WContainerWidget* parent)
: FilterWidget( parent ),
_db(db),
_tableView(nullptr),
_trackStats(nullptr)
TrackView::TrackView( Database::Handler& db, Wt::WContainerWidget* parent)
: Wt::WTableView( parent ),
_db(db)
{
// ----- TRACK -----
_queryModel.setQuery(_db.getSession().query<ResultType>("select track,release,artist from track,release,artist where track.release_id = release.id and track.artist_id = artist.id" ).orderBy("artist.name,release.name,track.disc_number,track.track_number"));
_queryModel.addColumn( "artist.name", "Artist" );
_queryModel.addColumn( "release.name", "Album" );
@@ -48,56 +44,52 @@ _trackStats(nullptr)
_queryModel.addColumn( "track.original_date", "Original Date" );
_queryModel.addColumn( "track.genre_list", "Genres" );
_queryModel.setBatchSize(1000);
_queryModel.setBatchSize(250);
_tableView = new Wt::WTableView( this );
_tableView->resize(Wt::WLength::Auto, 400);
this->setSortingEnabled(true);
this->setSelectionMode(Wt::SingleSelection);
this->setAlternatingRowColors(true);
this->setModel(&_queryModel);
_tableView->setSortingEnabled(true);
_tableView->setSelectionMode(Wt::SingleSelection);
_tableView->setAlternatingRowColors(true);
_tableView->setModel(&_queryModel);
this->setColumnWidth(0, 180); // Artist
this->setColumnWidth(1, 180); // Album
this->setColumnWidth(2, 70); // Disc Number
this->setColumnWidth(3, 70); // Track Number
this->setColumnWidth(4, 180); // Track
this->setColumnWidth(5, 70); // Duration
this->setColumnWidth(6, 70); // Date
this->setColumnWidth(7, 70); // Original Date
this->setColumnWidth(8, 180); // Genres
// this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical);
// Duration display
{
// TODO better handle 1 hour+ files!
Wt::WItemDelegate *delegate = new Wt::WItemDelegate(this);
delegate->setTextFormat("mm:ss");
_tableView->setItemDelegateForColumn(5, delegate);
this->setItemDelegateForColumn(5, delegate);
}
// Date display, just the year
{
Wt::WItemDelegate *delegate = new Wt::WItemDelegate(this);
delegate->setTextFormat("yyyy");
_tableView->setItemDelegateForColumn(6, delegate);
this->setItemDelegateForColumn(6, delegate);
}
{
Wt::WItemDelegate *delegate = new Wt::WItemDelegate(this);
delegate->setTextFormat("yyyy");
_tableView->setItemDelegateForColumn(7, delegate);
this->setItemDelegateForColumn(7, delegate);
}
// TODO other event!
_tableView->selectionChanged().connect(this, &TrackWidget::handleTrackSelected);
new Wt::WBreak(this);
_trackStats = new Wt::WText(this);
updateStats();
}
void
TrackWidget::updateStats(void)
{
std::ostringstream oss; oss << "Files :" << _tableView->model()->rowCount();
// TODO from UTF-8 ?
_trackStats->setText(oss.str());
this->selectionChanged().connect(this, &TrackView::handleTrackSelected);
}
// Set constraints created by parent filters
void
TrackWidget::refresh(const Constraint& constraint)
TrackView::refresh(const Constraint& constraint)
{
SqlQuery sqlQuery;
@@ -119,13 +111,12 @@ TrackWidget::refresh(const Constraint& constraint)
_queryModel.setQuery( query, true );
updateStats();
}
void
TrackWidget::handleTrackSelected(void)
TrackView::handleTrackSelected(void)
{
Wt::WModelIndexSet indexSet = _tableView->selectedIndexes();
Wt::WModelIndexSet indexSet = this->selectedIndexes();
if (!indexSet.empty()) {
Wt::WModelIndex currentIndex( *indexSet.begin() );
@@ -144,15 +135,15 @@ TrackWidget::handleTrackSelected(void)
}
void
TrackWidget::selectNextTrack(void)
TrackView::selectNextTrack(void)
{
Wt::WModelIndexSet indexSet = _tableView->selectedIndexes();
Wt::WModelIndexSet indexSet = this->selectedIndexes();
if (!indexSet.empty()) {
Wt::WModelIndex currentIndex( *indexSet.begin() );
// Check there are remainin tracks!
if (currentIndex.isValid() && _tableView->model()->rowCount() > currentIndex.row() + 1)
if (currentIndex.isValid() && this->model()->rowCount() > currentIndex.row() + 1)
{
_tableView->select( _tableView->model()->index( currentIndex.row() + 1, currentIndex.column()));
this->select( this->model()->index( currentIndex.row() + 1, currentIndex.column()));
ResultType result = _queryModel.resultRow( currentIndex.row() + 1 );
@@ -17,8 +17,8 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TRACK_WIDGET_HPP
#define TRACK_WIDGET_HPP
#ifndef TRACK_HPP
#define TRACK_HPP
#include <Wt/WTableView>
#include <Wt/Dbo/QueryModel>
@@ -26,15 +26,15 @@
#include "database/DatabaseHandler.hpp"
#include "database/AudioTypes.hpp"
#include "FilterWidget.hpp"
#include "Filter.hpp"
namespace UserInterface {
class TrackWidget : public FilterWidget
class TrackView : public Wt::WTableView, public Filter
{
public:
TrackWidget( Database::Handler& db, Wt::WContainerWidget* parent = 0);
TrackView( Database::Handler& db, Wt::WContainerWidget* parent = 0);
// Set constraints created by parent filters
void refresh(const Constraint& constraint);
@@ -59,10 +59,6 @@ class TrackWidget : public FilterWidget
Wt::Dbo::QueryModel< ResultType > _queryModel;
Wt::WTableView* _tableView;
void updateStats(void);
Wt::WText* _trackStats;
};
} // namespace UserInterface