From 49d2475422d155d5ed63eb72f710f2e73fedc3d1 Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 17 Oct 2014 00:11:53 +0200 Subject: [PATCH] WIP. new layout on the audio view --- src/Makefile.am | 94 ++++++------- src/remote/server/AuthRequestHandler.cpp | 3 + src/ui/LmsApplication.cpp | 97 +++++++------- src/ui/LmsApplication.hpp | 2 + src/ui/LmsHome.cpp | 42 +----- src/ui/audio/AudioDatabaseWidget.cpp | 126 ------------------ src/ui/audio/AudioDatabaseWidget.hpp | 62 --------- src/ui/audio/AudioWidget.cpp | 117 ++++++++++------ src/ui/audio/AudioWidget.hpp | 19 +-- src/ui/audio/{FilterWidget.hpp => Filter.hpp} | 21 ++- src/ui/audio/FilterChain.cpp | 76 +++++++++++ src/ui/audio/FilterChain.hpp | 57 ++++++++ ...lterWidget.cpp => KeywordSearchFilter.cpp} | 11 +- ...lterWidget.hpp => KeywordSearchFilter.hpp} | 13 +- ...{TableFilterWidget.cpp => TableFilter.cpp} | 45 ++++--- ...{TableFilterWidget.hpp => TableFilter.hpp} | 17 +-- .../audio/{TrackWidget.cpp => TrackView.cpp} | 71 +++++----- .../audio/{TrackWidget.hpp => TrackView.hpp} | 14 +- test/RemoteClient.cpp | 2 +- 19 files changed, 412 insertions(+), 477 deletions(-) delete mode 100644 src/ui/audio/AudioDatabaseWidget.cpp delete mode 100644 src/ui/audio/AudioDatabaseWidget.hpp rename src/ui/audio/{FilterWidget.hpp => Filter.hpp} (74%) create mode 100644 src/ui/audio/FilterChain.cpp create mode 100644 src/ui/audio/FilterChain.hpp rename src/ui/audio/{SearchFilterWidget.cpp => KeywordSearchFilter.cpp} (81%) rename src/ui/audio/{SearchFilterWidget.hpp => KeywordSearchFilter.hpp} (82%) rename src/ui/audio/{TableFilterWidget.cpp => TableFilter.cpp} (75%) rename src/ui/audio/{TableFilterWidget.hpp => TableFilter.hpp} (75%) rename src/ui/audio/{TrackWidget.cpp => TrackView.cpp} (70%) rename src/ui/audio/{TrackWidget.hpp => TrackView.hpp} (86%) diff --git a/src/Makefile.am b/src/Makefile.am index 9b24e69f..ec61c7d1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/remote/server/AuthRequestHandler.cpp b/src/remote/server/AuthRequestHandler.cpp index e353f82c..f8f225c6 100644 --- a/src/remote/server/AuthRequestHandler.cpp +++ b/src/remote/server/AuthRequestHandler.cpp @@ -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; } diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index ccd442c2..3e8367c8 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -18,8 +18,24 @@ */ #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 diff --git a/src/ui/LmsApplication.hpp b/src/ui/LmsApplication.hpp index 2ad74c83..dfde66f8 100644 --- a/src/ui/LmsApplication.hpp +++ b/src/ui/LmsApplication.hpp @@ -44,6 +44,8 @@ class LmsApplication : public Wt::WApplication SessionData _sessionData; LmsHome* _home; + AudioWidget* _audioWidget; + VideoWidget* _videoWidget; }; diff --git a/src/ui/LmsHome.cpp b/src/ui/LmsHome.cpp index 28279e2c..d7433a78 100644 --- a/src/ui/LmsHome.cpp +++ b/src/ui/LmsHome.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #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); } diff --git a/src/ui/audio/AudioDatabaseWidget.cpp b/src/ui/audio/AudioDatabaseWidget.cpp deleted file mode 100644 index 342a3f94..00000000 --- a/src/ui/audio/AudioDatabaseWidget.cpp +++ /dev/null @@ -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 . - */ - -#include - -#include // TODO -#include // 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(_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(_filters.back() ) ); - - trackWidget->selectNextTrack(); -} - -} // namespace UserInterface - - diff --git a/src/ui/audio/AudioDatabaseWidget.hpp b/src/ui/audio/AudioDatabaseWidget.hpp deleted file mode 100644 index 9117cea5..00000000 --- a/src/ui/audio/AudioDatabaseWidget.hpp +++ /dev/null @@ -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 . - */ - -#ifndef AUDIO_DB_WIDGET_HPP -#define AUDIO_DB_WIDGET_HPP - -#include - -#include -#include - -#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 _filters; // Free Search, Genre, Artist, Release, etc. - - bool _refreshingFilters; - -}; - -} // namespace UserInterface - -#endif - diff --git a/src/ui/audio/AudioWidget.cpp b/src/ui/audio/AudioWidget.cpp index d8ae157a..3d395785 100644 --- a/src/ui/audio/AudioWidget.cpp +++ b/src/ui/audio/AudioWidget.cpp @@ -17,10 +17,20 @@ * along with LMS. If not, see . */ -#include +#include + +#include +#include +#include +#include +#include #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 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()); - } - - _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 diff --git a/src/ui/audio/AudioWidget.hpp b/src/ui/audio/AudioWidget.hpp index f5009163..1f922d8c 100644 --- a/src/ui/audio/AudioWidget.hpp +++ b/src/ui/audio/AudioWidget.hpp @@ -22,13 +22,13 @@ #include -#include -#include #include -#include -#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; }; diff --git a/src/ui/audio/FilterWidget.hpp b/src/ui/audio/Filter.hpp similarity index 74% rename from src/ui/audio/FilterWidget.hpp rename to src/ui/audio/Filter.hpp index 5ae46548..162cca41 100644 --- a/src/ui/audio/FilterWidget.hpp +++ b/src/ui/audio/Filter.hpp @@ -17,35 +17,30 @@ * along with LMS. If not, see . */ -#ifndef FILTER_WIDGET_HPP -#define FILTER_WIDGET_HPP - -#include -#include -#include +#ifndef FILTER_HPP +#define FILTER_HPP #include -#include #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 diff --git a/src/ui/audio/FilterChain.cpp b/src/ui/audio/FilterChain.cpp new file mode 100644 index 00000000..b096fd08 --- /dev/null +++ b/src/ui/audio/FilterChain.cpp @@ -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 . + */ + +#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 + diff --git a/src/ui/audio/FilterChain.hpp b/src/ui/audio/FilterChain.hpp new file mode 100644 index 00000000..8ffd157a --- /dev/null +++ b/src/ui/audio/FilterChain.hpp @@ -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 . + */ + +#ifndef FILTER_CHAIN_HPP +#define FILTER_CHAIN_HPP + +#include + +#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 _filters; + + bool _refreshingFilters; +}; + +} // namespace UserInterface + +#endif diff --git a/src/ui/audio/SearchFilterWidget.cpp b/src/ui/audio/KeywordSearchFilter.cpp similarity index 81% rename from src/ui/audio/SearchFilterWidget.cpp rename to src/ui/audio/KeywordSearchFilter.cpp index 3af80e4c..f90ed90e 100644 --- a/src/ui/audio/SearchFilterWidget.cpp +++ b/src/ui/audio/KeywordSearchFilter.cpp @@ -17,19 +17,16 @@ * along with LMS. If not, see . */ -#include - -#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()) { diff --git a/src/ui/audio/SearchFilterWidget.hpp b/src/ui/audio/KeywordSearchFilter.hpp similarity index 82% rename from src/ui/audio/SearchFilterWidget.hpp rename to src/ui/audio/KeywordSearchFilter.hpp index 1da8c38c..a38f6382 100644 --- a/src/ui/audio/SearchFilterWidget.hpp +++ b/src/ui/audio/KeywordSearchFilter.hpp @@ -17,18 +17,19 @@ * along with LMS. If not, see . */ -#ifndef SEARCH_FILTER_WIDGET_PP -#define SEARCH_FILTER_WIDGET_PP +#ifndef KEYWORD_SEARCH_FILTER_HPP +#define KEYWORD_SEARCH_FILTER_HPP -#include +#include -#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); diff --git a/src/ui/audio/TableFilterWidget.cpp b/src/ui/audio/TableFilter.cpp similarity index 75% rename from src/ui/audio/TableFilterWidget.cpp rename to src/ui/audio/TableFilter.cpp index 0fbd746b..1e802ef3 100644 --- a/src/ui/audio/TableFilterWidget.cpp +++ b/src/ui/audio/TableFilter.cpp @@ -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 '',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; diff --git a/src/ui/audio/TableFilterWidget.hpp b/src/ui/audio/TableFilter.hpp similarity index 75% rename from src/ui/audio/TableFilterWidget.hpp rename to src/ui/audio/TableFilter.hpp index 666e6f2f..b9ad55a7 100644 --- a/src/ui/audio/TableFilterWidget.hpp +++ b/src/ui/audio/TableFilter.hpp @@ -17,29 +17,31 @@ * along with LMS. If not, see . */ -#ifndef TABLE_VIEW_FILTER_WIDGET_HPP -#define TABLE_VIEW_FILTER_WIDGET_HPP +#ifndef TABLE_FILTER_HPP +#define TABLE_FILTER_HPP #include #include -#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 ResultType; Wt::Dbo::QueryModel< ResultType > _queryModel; - Wt::WTableView* _tableView; }; } // namespace UserInterface diff --git a/src/ui/audio/TrackWidget.cpp b/src/ui/audio/TrackView.cpp similarity index 70% rename from src/ui/audio/TrackWidget.cpp rename to src/ui/audio/TrackView.cpp index 8f1f1bb4..fc45a8bf 100644 --- a/src/ui/audio/TrackWidget.cpp +++ b/src/ui/audio/TrackView.cpp @@ -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("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 ); diff --git a/src/ui/audio/TrackWidget.hpp b/src/ui/audio/TrackView.hpp similarity index 86% rename from src/ui/audio/TrackWidget.hpp rename to src/ui/audio/TrackView.hpp index a02cdfcc..226968af 100644 --- a/src/ui/audio/TrackWidget.hpp +++ b/src/ui/audio/TrackView.hpp @@ -17,8 +17,8 @@ * along with LMS. If not, see . */ -#ifndef TRACK_WIDGET_HPP -#define TRACK_WIDGET_HPP +#ifndef TRACK_HPP +#define TRACK_HPP #include #include @@ -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 diff --git a/test/RemoteClient.cpp b/test/RemoteClient.cpp index 23f3b384..d90cd23c 100644 --- a/test/RemoteClient.cpp +++ b/test/RemoteClient.cpp @@ -750,7 +750,7 @@ std::set tests = { Test::ReleaseFilterGenre, // Test::TrackFilters, // Test::Cover, -// Test::Transcode, + Test::Transcode, }; bool test(Test t)