diff --git a/Makefile.am b/Makefile.am index a82a941d..ae035de4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -17,7 +17,13 @@ lms_images_DATA = \ docroot/images/unknown-artist.jpg lms_approot_DATA = \ - approot/templates.xml + approot/artists.xml \ + approot/artist.xml \ + approot/messages.xml \ + approot/releases.xml \ + approot/release.xml \ + approot/templates.xml \ + approot/tracks.xml sysconf_DATA = \ $(top_srcdir)/conf/lms.conf diff --git a/README.md b/README.md index d60a242d..8f985bb4 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,7 @@ LMS is a self-hosted media streaming software, released under the GPLv3 license. It allows you to access your music and your videos using an http(s) web interface. ## Features - - Winamp-like interface, suited for large media collections - - Audio/Video transcode for maximum interoperability and low bandwith requirements + - Audio transcode for maximum interoperability and low bandwith requirements - User management - Playlist support - MusicBrainzID support to handle duplicated artist and release names @@ -14,20 +13,13 @@ Tags are automatically created to facilitate music searching, using: - Multi genre information from metadata - High-level information from AcousticBrainz -Please note LMS is still under development and will gain more features in the future: - - Radio function - - Interface suited for mobile devices - - Video support - - Subsonic and/or Ampache API - LMS is written entirely in C++. Therefore, it is suitable to run on embedded devices, where space and memory are limited. -Please note some media files may require significant CPU usage to be transcoded. ## Dependencies ### Debian ```sh -$ apt-get install g++ autoconf automake libboost-dev libboost-locale-dev libboost-iostreams-dev libavcodec-dev libwtdbosqlite-dev libwthttp-dev libwtdbo-dev libwt-dev libmagick++-dev libavcodec-dev libavformat-dev libav-tools libpstreams-dev libcurl-dev libcurlpp-dev libconfig++-dev +$ apt-get install g++ autoconf automake libboost-dev libboost-locale-dev libboost-iostreams-dev libavcodec-dev libwtdbosqlite-dev libwthttp-dev libwtdbo-dev libwt-dev libmagick++-dev libavcodec-dev libavformat-dev libav-tools libpstreams-dev libcurl-dev libcurlpp-dev libconfig++-dev ffmpeg ``` ## Build diff --git a/approot/artist.xml b/approot/artist.xml new file mode 100644 index 00000000..a877c111 --- /dev/null +++ b/approot/artist.xml @@ -0,0 +1,25 @@ + + + + + + + ${releases class="list-group"} + + + +
+
+ ${cover class="media-object"} +
+
+

${name}

+
+ ${}${tr:msg-various-artists}${} +

${}${year}${}${} (${orig-year})${}

+
+
+ +
diff --git a/approot/artists.xml b/approot/artists.xml new file mode 100644 index 00000000..92696a37 --- /dev/null +++ b/approot/artists.xml @@ -0,0 +1,25 @@ + + + + + + +
+
+ + ${search} +
+
+ ${artists class="list-group"} +
+ + +
+ ${nb-release} + ${name} +
+
+ +
diff --git a/approot/messages.xml b/approot/messages.xml new file mode 100644 index 00000000..196a7296 --- /dev/null +++ b/approot/messages.xml @@ -0,0 +1,13 @@ + + + + +Various artists +Artists +Albums +Tracks +Playlist +Add filter +Search... + + diff --git a/approot/release.xml b/approot/release.xml new file mode 100644 index 00000000..e92c6289 --- /dev/null +++ b/approot/release.xml @@ -0,0 +1,25 @@ + + + + + + + ${cover} + + ${tracks} + + + + ${}${disc-number}-${}${}${track-number}. ${} + ${}${artist-name} - ${} + ${name} + ${play-btn}${add-btn} + + + + diff --git a/approot/releases.xml b/approot/releases.xml new file mode 100644 index 00000000..a7cca098 --- /dev/null +++ b/approot/releases.xml @@ -0,0 +1,31 @@ + + + + + + + +
+
+ + ${search} +
+
+ ${releases class="list-group"} +
+ + +
+
+ ${cover class="media-object"} +
+
+

${release-name}

+ ${artist-name} +
+
+
+ +
diff --git a/approot/templates.xml b/approot/templates.xml index 007c9824..9894b02e 100644 --- a/approot/templates.xml +++ b/approot/templates.xml @@ -2,15 +2,23 @@ - -
- ${collapse-button} - - ${contents} + + ${navbar-top class="main-nav"} + ${contents class="container"} + + + + ${tr:msg-home-welcome} + + + + ${filters} + ${contents} + + + +
+ ${add-filter}${filters}
diff --git a/approot/tracks.xml b/approot/tracks.xml new file mode 100644 index 00000000..047796e8 --- /dev/null +++ b/approot/tracks.xml @@ -0,0 +1,33 @@ + + + + + + + +
+
+ + ${search} +
+
+ ${tracks class="list-group"} +
+ + +
+
+ ${cover class="media-object"} +
+
+

${name}

+
+ ${}${release-name}${} + ${}${artist-name}${} + ${play-btn}${add-btn} +
+
+ +
diff --git a/docroot/css/lms.css b/docroot/css/lms.css index 814622af..9c47a5dc 100644 --- a/docroot/css/lms.css +++ b/docroot/css/lms.css @@ -1,14 +1,5 @@ .main-nav { margin-bottom: 0px; - box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4); -} - -.Wt-hrh2 { - background-color: white; -} - -.Wt-vrh2 { - background-color: white; } .playqueue { @@ -198,26 +189,3 @@ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.5) } -.release_truncated-name { - white-space: nowrap; - overflow: hidden; - overflow-x: hidden; - overflow-y: hidden; - text-overflow: ellipsis; -} - -.release_img-responsive { - display: block; - max-width: 100%; - height: auto; -} - -.release-search-cover { - width: 512px; - min-width: 512px; - height: 512px; - min-height: 512px; - display: flex; - justify-content: space-between; - align-items: center; -} diff --git a/src/Makefile.am b/src/Makefile.am index 6de2fd19..02bd8c3b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -5,7 +5,7 @@ lms_SOURCES = \ $(srcdir)/av/AvInfo.cpp \ $(srcdir)/av/AvTranscoder.cpp \ $(srcdir)/cover/CoverArtGrabber.cpp \ - $(srcdir)/database/Artist.cpp \ + $(srcdir)/database/DbArtist.cpp \ $(srcdir)/database/DatabaseHandler.cpp \ $(srcdir)/database/MediaDirectory.cpp \ $(srcdir)/database/Playlist.cpp \ @@ -24,41 +24,22 @@ lms_SOURCES = \ $(srcdir)/image/Image.cpp \ $(srcdir)/metadata/AvFormat.cpp \ $(srcdir)/metadata/TagLibParser.cpp \ + $(srcdir)/ui/ArtistView.cpp \ + $(srcdir)/ui/ArtistsView.cpp \ + $(srcdir)/ui/Explore.cpp \ + $(srcdir)/ui/Filters.cpp \ + $(srcdir)/ui/HomeView.cpp \ $(srcdir)/ui/LmsApplication.cpp \ + $(srcdir)/ui/ReleasesView.cpp \ + $(srcdir)/ui/ReleaseView.cpp \ + $(srcdir)/ui/TracksView.cpp \ $(srcdir)/ui/auth/LmsAuth.cpp \ - $(srcdir)/ui/audio/AudioPlayer.cpp \ - $(srcdir)/ui/audio/desktop/DesktopAudio.cpp \ - $(srcdir)/ui/audio/desktop/FilterChain.cpp \ - $(srcdir)/ui/audio/desktop/KeywordSearchFilter.cpp \ - $(srcdir)/ui/audio/desktop/PlayQueue.cpp \ - $(srcdir)/ui/audio/desktop/TableFilter.cpp \ - $(srcdir)/ui/audio/desktop/TrackView.cpp \ - $(srcdir)/ui/audio/mobile/ArtistSearch.cpp \ - $(srcdir)/ui/audio/mobile/ArtistSearchView.cpp \ - $(srcdir)/ui/audio/mobile/ArtistView.cpp \ - $(srcdir)/ui/audio/mobile/MobileAudio.cpp \ - $(srcdir)/ui/audio/mobile/MobilePlayQueue.cpp \ - $(srcdir)/ui/audio/mobile/PreviewSearchView.cpp \ - $(srcdir)/ui/audio/mobile/ReleaseSearch.cpp \ - $(srcdir)/ui/audio/mobile/ReleaseSearchView.cpp \ - $(srcdir)/ui/audio/mobile/ReleaseView.cpp \ - $(srcdir)/ui/audio/mobile/TrackSearch.cpp \ - $(srcdir)/ui/audio/mobile/TrackSearchView.cpp \ $(srcdir)/ui/common/DirectoryValidator.cpp \ $(srcdir)/ui/common/InputRange.cpp \ $(srcdir)/ui/common/LineEdit.cpp \ $(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \ $(srcdir)/ui/resource/ImageResource.cpp \ $(srcdir)/ui/resource/TranscodeResource.cpp \ - $(srcdir)/ui/settings/Settings.cpp \ - $(srcdir)/ui/settings/SettingsAccountFormView.cpp \ - $(srcdir)/ui/settings/SettingsAudioFormView.cpp \ - $(srcdir)/ui/settings/SettingsDatabaseFormView.cpp \ - $(srcdir)/ui/settings/SettingsFirstConnectionFormView.cpp \ - $(srcdir)/ui/settings/SettingsMediaDirectories.cpp \ - $(srcdir)/ui/settings/SettingsMediaDirectoryFormView.cpp \ - $(srcdir)/ui/settings/SettingsUserFormView.cpp \ - $(srcdir)/ui/settings/SettingsUsers.cpp \ $(srcdir)/utils/Config.cpp \ $(srcdir)/utils/Logger.cpp \ $(srcdir)/utils/Path.cpp \ diff --git a/src/cover/CoverArtGrabber.cpp b/src/cover/CoverArtGrabber.cpp index a43f3626..a73a502c 100644 --- a/src/cover/CoverArtGrabber.cpp +++ b/src/cover/CoverArtGrabber.cpp @@ -178,11 +178,7 @@ Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type re if (!release || release->isNone()) return std::vector(); - // Get the first track of the release - std::vector tracks = Track::getByFilter(session, - SearchFilter::ById(SearchFilter::Field::Release, releaseId), - -1, 1 /* limit result size */); - + std::vector tracks = release->getTracks(); if (tracks.empty()) return std::vector(); diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index c159d378..1aedb3b8 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -187,7 +187,7 @@ Handler::createConnectionPool(boost::filesystem::path p) connection->executeSql("pragma journal_mode=WAL"); - // connection->setProperty("show-queries", "true"); + connection->setProperty("show-queries", "true"); return new Wt::Dbo::FixedSqlConnectionPool(connection, 1); } diff --git a/src/database/DatabaseHandler.hpp b/src/database/DatabaseHandler.hpp index 38978325..6b07984e 100644 --- a/src/database/DatabaseHandler.hpp +++ b/src/database/DatabaseHandler.hpp @@ -33,6 +33,7 @@ namespace Database { +typedef Wt::Dbo::dbo_default_traits::IdType id_type; typedef Wt::Auth::Dbo::UserDatabase UserDatabase; // Session living class handling the database and the login diff --git a/src/database/DbArtist.cpp b/src/database/DbArtist.cpp new file mode 100644 index 00000000..7dc341f6 --- /dev/null +++ b/src/database/DbArtist.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2015 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 "Types.hpp" +#include "SqlQuery.hpp" + +#include "utils/Logger.hpp" + +namespace Database +{ + +Artist::Artist(const std::string& name, const std::string& MBID) +: _name(std::string(name, 0 , _maxNameLength)), +_MBID(MBID) +{ + +} + +std::vector +Artist::getByName(Wt::Dbo::Session& session, const std::string& name) +{ + Wt::Dbo::collection res = session.find().where("name = ?").bind( std::string(name, 0, _maxNameLength) ); + return std::vector(res.begin(), res.end()); +} + +Artist::pointer +Artist::getByMBID(Wt::Dbo::Session& session, const std::string& mbid) +{ + return session.find().where("mbid = ?").bind(mbid); +} + +Artist::pointer +Artist::getById(Wt::Dbo::Session& session, Artist::id_type id) +{ + return session.find().where("id = ?").bind(id); +} + +Artist::pointer +Artist::create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID) +{ + return session.add(new Artist(name, MBID)); +} + +Artist::pointer +Artist::getNone(Wt::Dbo::Session& session) +{ + std::vector res = getByName(session, ""); + if (res.empty()) + return create(session, ""); + + return res.front(); +} + +bool +Artist::isNone() const +{ + return _name == ""; +} + +std::vector +Artist::getAll(Wt::Dbo::Session& session, int offset, int size) +{ + Wt::Dbo::collection res = session.find().orderBy("LOWER(name)").offset(offset).limit(size); + return std::vector(res.begin(), res.end()); +} + +std::vector +Artist::getAllOrphans(Wt::Dbo::Session& session) +{ + Wt::Dbo::collection res = session.query< Wt::Dbo::ptr >("SELECT DISTINCT a FROM artist a LEFT OUTER JOIN Track t ON a.id = t.artist_id WHERE t.id IS NULL"); + + return std::vector(res.begin(), res.end()); +} + +static +Wt::Dbo::Query +getQuery(Wt::Dbo::Session& session, + const std::vector& clusterIds, + const std::vector& keywords) +{ + WhereClause where; + + std::ostringstream oss; + oss << "SELECT DISTINCT a FROM artist a"; + + for (auto keyword : keywords) + where.And(WhereClause("a.name LIKE ?")).bind("%%" + keyword + "%%"); + + if (!clusterIds.empty()) + { + oss << " INNER JOIN track t ON t.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; + + WhereClause clusterClause; + + for (auto id : clusterIds) + clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id)); + + where.And(clusterClause); + } + oss << " " << where.get(); + + if (!clusterIds.empty()) + oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size(); + + oss << " ORDER BY a.name"; + + Wt::Dbo::Query query = session.query( oss.str() ); + + for (const std::string& bindArg : where.getBindArgs()) + { + query.bind(bindArg); + } + + return query; +} + +std::vector +Artist::getByFilter(Wt::Dbo::Session& session, + const std::vector& clusters, + const std::vector keywords, + int offset, int size, bool& moreResults) +{ + Wt::Dbo::collection collection = getQuery(session, clusters, keywords).limit(size).offset(offset); + + auto res = std::vector(collection.begin(), collection.end()); + + if (size != -1 && res.size() == static_cast(size) + 1) + { + moreResults = true; + res.pop_back(); + } + else + moreResults = false; + + return res; +} + +std::vector > +Artist::getReleases(const std::vector& clusterIds) const +{ + assert(self()); + assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); + assert(session()); + + WhereClause where; + + std::ostringstream oss; + oss << "SELECT DISTINCT r FROM release r INNER JOIN artist a ON t.artist_id = a.id INNER JOIN track t ON t.release_id = r.id"; + + if (!clusterIds.empty()) + { + oss << " INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; + + WhereClause clusterClause; + + for (auto id : clusterIds) + clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id)); + + where.And(clusterClause); + } + + where.And(WhereClause("a.id = ?")).bind(std::to_string(id())); + + oss << " " << where.get(); + + if (!clusterIds.empty()) + oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size(); + + // TODO order + oss << " ORDER BY t.date,r.name"; + + Wt::Dbo::Query query = session()->query( oss.str() ); + + for (const std::string& bindArg : where.getBindArgs()) + { + query.bind(bindArg); + } + + Wt::Dbo::collection< Wt::Dbo::ptr > res = query; + + return std::vector< Wt::Dbo::ptr > (res.begin(), res.end()); +} + + + +} // namespace Database diff --git a/src/database/DbArtist.hpp b/src/database/DbArtist.hpp new file mode 100644 index 00000000..7b45bcb6 --- /dev/null +++ b/src/database/DbArtist.hpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2015 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 _DB_ARTIST_HPP_ +#define _DB_ARTIST_HPP_ + +#include +#include + +#include +#include + +#include "SearchFilter.hpp" + +namespace Database +{ + +class Track; +class Cluster; +class Release; + +class Artist : public Wt::Dbo::Dbo +{ + public: + + typedef Wt::Dbo::ptr pointer; + typedef Wt::Dbo::dbo_traits::IdType id_type; + + Artist() {} + Artist(const std::string& name, const std::string& MBID = ""); + + // Accessors + static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID); + static pointer getById(Wt::Dbo::Session& session, id_type id); + static pointer getNone(Wt::Dbo::Session& session); // Special entry + static std::vector getByName(Wt::Dbo::Session& session, const std::string& name); + static std::vector getByFilter(Wt::Dbo::Session& session, + const std::vector& clusters, // at least one track that belongs to these clusters + const std::vector keywords, // name must match all of these keywords + int offset, + int size, + bool& moreExpected); + + static std::vector getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1); + static std::vector getAllOrphans(Wt::Dbo::Session& session); // No track related + + // Accessors + std::string getName(void) const { return _name; } + std::string getMBID(void) const { return _MBID; } + + // Get the releases that have at least one track for this artist + belongs to optonal cluster filters + std::vector> getReleases(const std::vector& clusterIds = std::vector()) const; + + void setMBID(std::string mbid) { _MBID = mbid; } + + // Create + static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = ""); + + bool isNone(void) const; + + template + void persist(Action& a) + { + Wt::Dbo::field(a, _name, "name"); + Wt::Dbo::field(a, _MBID, "mbid"); + + Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "artist"); + } + + private: + + static const std::size_t _maxNameLength = 128; + + std::string _name; + std::string _MBID; // Musicbrainz Identifier + + Wt::Dbo::collection> _tracks; // Tracks of this artist +}; + +} // namespace Database + +#endif diff --git a/src/database/Release.cpp b/src/database/Release.cpp index d82cbcef..ca9e1fb5 100644 --- a/src/database/Release.cpp +++ b/src/database/Release.cpp @@ -82,68 +82,61 @@ Release::getAll(Wt::Dbo::Session& session, int offset, int size) std::vector Release::getAllOrphans(Wt::Dbo::Session& session) { - Wt::Dbo::collection res = session.query< Wt::Dbo::ptr >("select r from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL"); + Wt::Dbo::collection res = session.query>("select r from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL"); return std::vector(res.begin(), res.end()); } +static Wt::Dbo::Query -Release::getQuery(Wt::Dbo::Session& session, SearchFilter filter) +getQuery(Wt::Dbo::Session& session, + const std::vector& clusterIds, + const std::vector keywords) { - SqlQuery sqlQuery = generatePartialQuery(filter); + WhereClause where; - Wt::Dbo::Query query - = session.query("SELECT r FROM release r INNER JOIN artist a ON a.id = t.artist_id INNER JOIN track t ON t.release_id = r.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id " + sqlQuery.where().get()).groupBy("r.id").orderBy("r.name"); + std::ostringstream oss; + oss << "SELECT DISTINCT r FROM release r"; - for (const std::string& bindArg : sqlQuery.where().getBindArgs()) - query.bind(bindArg); + for (auto keyword : keywords) + where.And(WhereClause("r.name LIKE ?")).bind("%%" + keyword + "%%"); - return query; -} - -Wt::Dbo::Query -Release::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) -{ - SqlQuery sqlQuery = generatePartialQuery(filter); - - // TODO DATE of RELEASE - Wt::Dbo::Query query - = session.query("SELECT r.id, r.name, t.date, COUNT(DISTINCT t.id) FROM release r INNER JOIN track t ON t.release_id = r.id INNER JOIN artist a ON a.id = t.artist_id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id " + sqlQuery.where().get()).groupBy("r.id").orderBy("r.name"); - - for (const std::string& bindArg : sqlQuery.where().getBindArgs()) - query.bind(bindArg); - - return query; -} - -void -Release::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) -{ - Wt::Dbo::Query query = getUIQuery(session, filter); - - model.setQuery(query, columnNames.empty() ? true : false); - - // TODO do something better - if (columnNames.size() == 3) + if (!clusterIds.empty()) { - model.addColumn( "r.name", columnNames[0]); - model.addColumn( "t.date", columnNames[1]); - model.addColumn( "COUNT(DISTINCT t.id)", columnNames[2]); + oss << " INNER JOIN track t ON t.release_id = r.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; + + WhereClause clusterClause; + + for (auto id : clusterIds) + clusterClause.And(WhereClause("c.id = ?")).bind(std::to_string(id)); + + where.And(clusterClause); } + + oss << " " << where.get(); + + if (!clusterIds.empty()) + oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size(); + + oss << " ORDER BY r.name"; + + Wt::Dbo::Query query = session.query( oss.str() ); + + for (const std::string& bindArg : where.getBindArgs()) + query.bind(bindArg); + + return query; } std::vector -Release::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) +Release::getByFilter(Wt::Dbo::Session& session, + const std::vector& clusterIds, + const std::vector keywords, + int offset, int size, bool& moreResults) { - Wt::Dbo::collection res = getQuery(session, filter).limit(size).offset(offset); + Wt::Dbo::collection collection = getQuery(session, clusterIds, keywords).limit(size).offset(offset); - return std::vector(res.begin(), res.end()); -} - -std::vector -Release::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool& moreResults) -{ - auto res = getByFilter(session, filter, offset, size + 1); + auto res = std::vector(collection.begin(), collection.end()); if (size != -1 && res.size() == static_cast(size) + 1) { @@ -156,28 +149,63 @@ Release::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, return res; } -int +boost::optional Release::getReleaseYear(bool original) const { assert(session()); - // TODO something better - auto tracks = Track::getByFilter(*session(), SearchFilter::ById(SearchFilter::Field::Release, this->id()), -1, 1); + Wt::Dbo::collection times = session()->query( + std::string("SELECT ") + (original ? "t.original_date" : "t.date") + " FROM track t INNER JOIN release r ON r.id = t.release_id") + .where("r.id = ?") + .groupBy("t.date") + .bind(this->id()); - if (tracks.empty()) - return 0; + /* various dates, no date */ + if (times.empty() || times.size() > 1) + return boost::none; - boost::gregorian::date date; - - if (original) - date = tracks.front()->getOriginalDate().date(); - else - date = tracks.front()->getDate().date(); + boost::gregorian::date date = times.front().date(); if (date.is_special()) - return 0; + return boost::none; - return date.year(); + return boost::make_optional(date.year()); } +std::vector> +Release::getArtists() const +{ + assert(self()); + assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); + assert(session()); + + Wt::Dbo::collection> res = session()->query>( + "SELECT DISTINCT a FROM artist a INNER JOIN release r ON t.artist_id = a.id INNER JOIN track t ON t.release_id = r.id") + .where("r.id = ?") + .bind(id()); + + return std::vector>(res.begin(), res.end()); +} + +bool +Release::hasVariousArtists() const +{ + // TODO optimize + return getArtists().size() > 1; +} + +std::vector> +Release::getTracks() const +{ + assert(self()); + assert(session()); + + Wt::Dbo::collection> res = session()->query>( + "SELECT t FROM track t INNER JOIN release r ON t.release_id = r.id") + .where("r.id = ?") + .orderBy("t.disc_number,t.track_number") + .bind(id()); + + return std::vector>(res.begin(), res.end()); +} } // namespace Database diff --git a/src/database/Release.hpp b/src/database/Release.hpp index f92b6c0c..fc6fedcb 100644 --- a/src/database/Release.hpp +++ b/src/database/Release.hpp @@ -17,11 +17,11 @@ * along with LMS. If not, see . */ -#ifndef _DB_RELEASE_HPP_ -#define _DB_RELEASE_HPP_ +#pragma once + +#include #include -#include #include "SearchFilter.hpp" @@ -30,6 +30,7 @@ namespace Database class Track; class Release; +class Artist; class Release : public Wt::Dbo::Dbo { @@ -46,30 +47,34 @@ class Release : public Wt::Dbo::Dbo static std::vector getByName(Wt::Dbo::Session& session, const std::string& name); static pointer getById(Wt::Dbo::Session& session, id_type id); static pointer getNone(Wt::Dbo::Session& session); // Special entry - static std::vector getAllOrphans(Wt::Dbo::Session& session); + static std::vector getAllOrphans(Wt::Dbo::Session& session); // no track related static std::vector getAll(Wt::Dbo::Session& session, int offset, int size); - static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); - static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool& moreExpected); + + static std::vector getByFilter(Wt::Dbo::Session& session, + const std::vector& clusters, // at least one track that belongs to these clusters + const std::vector keywords, // name must match all of these keywords + int offset, + int size, + bool& moreExpected); + + std::vector> getTracks() const; // Create static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = ""); // Utility functions - int getReleaseYear(bool originalDate = false) const; // 0 if unknown or various + boost::optional getReleaseYear(bool originalDate = false) const; // 0 if unknown or various - // MVC models for the user interface - // ID, Release name, year, track counts - typedef boost::tuple UIQueryResult; - static Wt::Dbo::Query getUIQuery(Wt::Dbo::Session& session, SearchFilter filter); - static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQueryResult >& model, SearchFilter filter, const std::vector& columnNames = std::vector()); - - - // Accessosrs + // Accessors std::string getName() const { return _name; } std::string getMBID() const { return _MBID; } bool isNone(void) const; boost::posix_time::time_duration getDuration(void) const; + // Get the artists of this release + std::vector > getArtists() const; + bool hasVariousArtists() const; + void setMBID(std::string mbid) { _MBID = mbid; } template @@ -82,8 +87,6 @@ class Release : public Wt::Dbo::Dbo } private: - static Wt::Dbo::Query getQuery(Wt::Dbo::Session& session, SearchFilter filter); - static const std::size_t _maxNameLength = 128; std::string _name; @@ -94,5 +97,4 @@ class Release : public Wt::Dbo::Dbo } // namespace Database -#endif diff --git a/src/database/SearchFilter.cpp b/src/database/SearchFilter.cpp index 194296e4..5fb23e94 100644 --- a/src/database/SearchFilter.cpp +++ b/src/database/SearchFilter.cpp @@ -24,98 +24,15 @@ namespace Database { -static std::ostream& operator<<(std::ostream& ost, const std::vector< Wt::Dbo::dbo_default_traits::IdType>& ids) +void +SearchFilter::operator+=(const SearchFilter& filter) { - const char *sep = ""; - - for (auto id : ids) - { - ost << sep << std::to_string(id); - sep = ","; - } - - return ost; } -SqlQuery generatePartialQuery(SearchFilter& filter) +SqlQuery +SearchFilter::generatePartialQuery() { SqlQuery sqlQuery; - - // Process name like parameters - for (auto nameLikeMatches : filter.nameLikeMatch) - { - WhereClause likeWhereClause; - - for (auto nameLikeMatch : nameLikeMatches) - { - switch (nameLikeMatch.first) - { - case SearchFilter::Field::Artist: - for (const std::string& name : nameLikeMatch.second) - likeWhereClause.Or( WhereClause("a.name LIKE ?") ).bind("%%" + name + "%%"); - break; - - case SearchFilter::Field::Release: - for (const std::string& name : nameLikeMatch.second) - likeWhereClause.Or( WhereClause("r.name LIKE ?") ).bind("%%" + name + "%%"); - break; - - case SearchFilter::Field::Cluster: - for (const std::string& name : nameLikeMatch.second) - likeWhereClause.Or( WhereClause("c.name LIKE ?") ).bind("%%" + name + "%%"); - break; - - case SearchFilter::Field::Track: - for (const std::string& name : nameLikeMatch.second) - likeWhereClause.Or( WhereClause("t.name LIKE ?") ).bind("%%" + name + "%%"); - break; - } - } - sqlQuery.where().And( likeWhereClause ); - } - - // Process id exact match parameters - // Id list may be long, we do not bind the parameters - // Safe since we already known these are just ints - for (auto idMatch : filter.idMatch) - { - WhereClause idWhereClause; - - switch (idMatch.first) - { - case SearchFilter::Field::Artist: - { - std::ostringstream oss; - oss << "a.id IN (" << idMatch.second << ")"; - idWhereClause.Or(oss.str()); - } - break; - case SearchFilter::Field::Release: - { - std::ostringstream oss; - oss << "r.id IN (" << idMatch.second << ")"; - idWhereClause.Or(oss.str()); - } - break; - case SearchFilter::Field::Cluster: - { - std::ostringstream oss; - oss << "c.id IN (" << idMatch.second << ")"; - idWhereClause.Or(oss.str()); - } - break; - case SearchFilter::Field::Track: - { - std::ostringstream oss; - oss << "t.id IN (" << idMatch.second << ")"; - idWhereClause.Or(oss.str()); - } - break; - } - - sqlQuery.where().And( idWhereClause ); - } - return sqlQuery; } diff --git a/src/database/SearchFilter.hpp b/src/database/SearchFilter.hpp index 2156ecfe..01bbcb06 100644 --- a/src/database/SearchFilter.hpp +++ b/src/database/SearchFilter.hpp @@ -31,71 +31,37 @@ namespace Database { +typedef Wt::Dbo::dbo_default_traits::IdType id_type; + class SearchFilter { public: - enum class Field { - Artist, // artist - Release, // release - Track, // track - Cluster, // cluster - }; - - typedef std::vector > > NameLikeMatchType; - typedef std::map > IdMatchType; + typedef int id_type; SearchFilter() {} - // Helpers - static SearchFilter IdMatch( const IdMatchType& _idMatch ) - { - return SearchFilter(_idMatch); - } + static SearchFilter Artist(std::string name) {return SearchFilter();} + static SearchFilter Artist(id_type id) {return SearchFilter();} - static SearchFilter NameLikeMatch( const NameLikeMatchType& _nameLikeMatch ) - { - return SearchFilter(_nameLikeMatch); - } + static SearchFilter Release(std::string name) {return SearchFilter();} + static SearchFilter Release(id_type id) {return SearchFilter();} - // Single Field ID match - static SearchFilter ById(Field field, Wt::Dbo::dbo_default_traits::IdType id) - { - return SearchFilter({{field, {id} }}); - } + static SearchFilter Track(std::string name) {return SearchFilter();} - // Single Field Name match OR - static SearchFilter ByNameOr(Field field, std::vector keywords) - { - return NameLikeMatch({{{field, keywords}}}); - } + static SearchFilter Cluster(id_type id) {return SearchFilter();} - // Single Field Name match AND - static SearchFilter ByNameAnd(Field field, std::vector keywords) - { - NameLikeMatchType nameLikeMatch; + // Combine search filters by add operation + // Caution: multiple filters on different artist/release/track + // values may lead to empty results + void operator+=(const SearchFilter& filter); - for (auto keyword : keywords) - nameLikeMatch.push_back({{{field, {keyword}}}}); - - return NameLikeMatch(nameLikeMatch); - } - - // The filter is a AND of the following conditions: - - // ((Field1.name LIKE STR1-1 OR Field1.name LIKE STR1-2 ...) OR (Field2.name LIKE STR2-1 OR Field2.name LIKE STR2-2 ...) ... - NameLikeMatchType nameLikeMatch; - - // (Field1.id IN (ID1-1,ID1-2 ... ) AND (Field2.id IN (ID2-1,ID2-2 ... ) ... - IdMatchType idMatch; + SqlQuery generatePartialQuery(); private: - SearchFilter(const NameLikeMatchType& _nameLikeMatch) : nameLikeMatch(_nameLikeMatch) {} - SearchFilter(const IdMatchType& _idMatch) : idMatch(_idMatch) {} }; -SqlQuery generatePartialQuery(SearchFilter& filter); } // namespace Database diff --git a/src/database/Track.cpp b/src/database/Track.cpp index cfb3f8ea..ac7faa18 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -108,29 +108,42 @@ Track::getClusters(void) const return clusters; } +static Wt::Dbo::Query< Track::pointer > -Track::getQuery(Wt::Dbo::Session& session, SearchFilter filter) +getQuery(Wt::Dbo::Session& session, + const std::vector& clusterIds, + const std::vector keywords) { - SqlQuery sqlQuery = generatePartialQuery(filter); + WhereClause where; - Wt::Dbo::Query query - = session.query( "SELECT t FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get()).groupBy("t.id").orderBy("a.name,t.date,r.name,t.disc_number,t.track_number"); + std::ostringstream oss; + oss << "SELECT t FROM track t"; - for (const std::string& bindArg : sqlQuery.where().getBindArgs()) - query.bind(bindArg); + for (auto keyword : keywords) + where.And(WhereClause("t.name LIKE ?")).bind("%%" + keyword + "%%"); - return query; -} + if (!clusterIds.empty()) + { + oss << " INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; -Wt::Dbo::Query< Track::UIQueryResult > -Track::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) -{ - SqlQuery sqlQuery = generatePartialQuery(filter); + WhereClause clusterClause; - Wt::Dbo::Query query - = session.query( "SELECT t.id, a.name, r.name, t.disc_number, t.track_number, t.name, t.duration, t.date, t.original_date, t.genre_list FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get()).groupBy("t.id").orderBy("a.name,t.date,r.name,t.disc_number,t.track_number"); + for (auto id : clusterIds) + clusterClause.And(WhereClause("c.id = ?")).bind(std::to_string(id)); - for (const std::string& bindArg : sqlQuery.where().getBindArgs()) + where.And(clusterClause); + } + + oss << " " << where.get(); + + if (!clusterIds.empty()) + oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size(); + + oss << " ORDER BY t.name"; + + Wt::Dbo::Query query = session.query( oss.str() ); + + for (const std::string& bindArg : where.getBindArgs()) query.bind(bindArg); return query; @@ -139,7 +152,7 @@ Track::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) Track::StatsQueryResult Track::getStats(Wt::Dbo::Session& session, SearchFilter filter) { - SqlQuery sqlQuery = generatePartialQuery(filter); + SqlQuery sqlQuery = filter.generatePartialQuery(); Wt::Dbo::Query query = session.query( "SELECT COUNT(\"id\"), SUM(\"dur\") FROM (SELECT t.id as \"id\", t.duration as \"dur\" FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get() + " GROUP BY t.id)"); @@ -151,17 +164,14 @@ Track::getStats(Wt::Dbo::Session& session, SearchFilter filter) std::vector -Track::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) +Track::getByFilter(Wt::Dbo::Session& session, + const std::vector& clusterIds, + const std::vector keywords, + int offset, int size, bool& moreResults) { - Wt::Dbo::collection res = getQuery(session, filter).limit(size).offset(offset); + Wt::Dbo::collection collection = getQuery(session, clusterIds, keywords).limit(size).offset(offset); - return std::vector(res.begin(), res.end()); -} - -std::vector -Track::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool& moreResults) -{ - auto res = getByFilter(session, filter, offset, size + 1); + auto res = std::vector(collection.begin(), collection.end()); if (size != -1 && res.size() == static_cast(size) + 1) { @@ -174,26 +184,28 @@ Track::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, i return res; } -void -Track::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQueryResult >& model, SearchFilter filter, const std::vector& columnNames) +boost::optional +Track::getTrackNumber(void) const { - Wt::Dbo::Query< UIQueryResult > query = getUIQuery(session, filter); - model.setQuery(query, columnNames.empty() ? true : false); + return (_trackNumber > 0) ? boost::make_optional(_trackNumber) : boost::none; +} - // TODO do something better - if (columnNames.size() == 9) - { - model.addColumn( "a.name", columnNames[0] ); - model.addColumn( "r.name", columnNames[1] ); - model.addColumn( "t.disc_number", columnNames[2] ); - model.addColumn( "t.track_number", columnNames[3] ); - model.addColumn( "t.name", columnNames[4] ); - model.addColumn( "t.duration", columnNames[5] ); - model.addColumn( "t.date", columnNames[6] ); - model.addColumn( "t.original_date", columnNames[7] ); - model.addColumn( "t.genre_list", columnNames[8] ); - } +boost::optional +Track::getTotalTrackNumber(void) const +{ + return (_totalTrackNumber > 0) ? boost::make_optional(_totalTrackNumber) : boost::none; +} +boost::optional +Track::getDiscNumber(void) const +{ + return (_discNumber > 0) ? boost::make_optional(_discNumber) : boost::none; +} + +boost::optional +Track::getTotalDiscNumber(void) const +{ + return (_totalDiscNumber > 0) ? boost::make_optional(_totalDiscNumber) : boost::none; } @@ -221,6 +233,13 @@ Cluster::get(Wt::Dbo::Session& session, std::string type, std::string name) return session.find().where("type = ?").where("name = ?").bind( std::string(type, 0, _maxTypeLength)).bind( std::string(name, 0, _maxNameLength)); } +std::vector +Cluster::getByType(Wt::Dbo::Session& session, std::string type) +{ + Wt::Dbo::collection res = session.find().where("type = ?").bind( std::string(type, 0, _maxTypeLength)).orderBy("name"); + return std::vector(res.begin(), res.end()); +} + Cluster::pointer Cluster::getNone(Wt::Dbo::Session& session) { @@ -252,7 +271,7 @@ Cluster::remove(Wt::Dbo::Session& session, std::string type) Wt::Dbo::Query Cluster::getQuery(Wt::Dbo::Session& session, SearchFilter filter) { - SqlQuery sqlQuery = generatePartialQuery(filter); + SqlQuery sqlQuery = filter.generatePartialQuery(); Wt::Dbo::Query query = session.query( "SELECT g FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id INNER JOIN artist a ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.id = t_c.track_id " + sqlQuery.where().get()).groupBy("c.name").orderBy("c.name"); @@ -263,34 +282,6 @@ Cluster::getQuery(Wt::Dbo::Session& session, SearchFilter filter) return query; } -Wt::Dbo::Query -Cluster::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) -{ - SqlQuery sqlQuery = generatePartialQuery(filter); - - Wt::Dbo::Query query - = session.query( "SELECT c.id, c.name, COUNT(DISTINCT t.id) FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id INNER JOIN artist a ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.id = t_c.track_id " + sqlQuery.where().get()).groupBy("c.name").orderBy("c.name"); - - for (const std::string& bindArg : sqlQuery.where().getBindArgs()) - query.bind(bindArg); - - return query; -} - -void -Cluster::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) -{ - Wt::Dbo::Query query = getUIQuery(session, filter); - model.setQuery(query, columnNames.empty() ? true : false); - - // TODO do something better - if (columnNames.size() == 2) - { - model.addColumn( "c.name", columnNames[0] ); - model.addColumn( "COUNT(DISTINCT t.id)", columnNames[1] ); - } -} - std::vector Cluster::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) { diff --git a/src/database/Track.hpp b/src/database/Track.hpp index 3d523814..d1af053c 100644 --- a/src/database/Track.hpp +++ b/src/database/Track.hpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -44,6 +45,12 @@ class Cluster { public: + enum class Type + { + Genre = 1, + Mood = 2, + }; + typedef Wt::Dbo::ptr pointer; typedef Wt::Dbo::dbo_traits::IdType id_type; @@ -57,12 +64,6 @@ class Cluster static Wt::Dbo::collection getAll(Wt::Dbo::Session& session); static std::vector getByType(Wt::Dbo::Session& session, std::string type); - // MVC models for the user interface - // ClusterID, type, name, track count - typedef boost::tuple UIQueryResult; - static Wt::Dbo::Query getUIQuery(Wt::Dbo::Session& session, SearchFilter filter); - static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames = std::vector()); - // Create utility static pointer create(Wt::Dbo::Session& session, std::string type, std::string name); @@ -118,8 +119,13 @@ class Track static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p); static pointer getById(Wt::Dbo::Session& session, id_type id); static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID); - static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); - static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool &moreResults); + static std::vector getByFilter(Wt::Dbo::Session& session, + const std::vector& clusters, // tracks that belong to these clusters + const std::vector keywords, // name must match all of these keywords + int offset, + int size, + bool& moreExpected); + static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session); static std::vector getAllIds(Wt::Dbo::Session& session); // nested transaction static std::vector getAllPaths(Wt::Dbo::Session& session); // nested transaction @@ -127,22 +133,6 @@ class Track static std::vector getChecksumDuplicates(Wt::Dbo::Session& session); // Utility fonctions - // MVC models for the user interface - // ID, Artist name, Release Name, DiscNumber, TrackNumber, Name, duration, date, original date, genre list - typedef boost::tuple // genre list - UIQueryResult; - static Wt::Dbo::Query< UIQueryResult > getUIQuery(Wt::Dbo::Session& session, SearchFilter filter); - static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQueryResult >& model, SearchFilter filter, const std::vector& columnNames = std::vector()); - // Stats for a given search filter typedef boost::tuple< int, // Total tracks @@ -174,10 +164,10 @@ class Track void setArtist(Wt::Dbo::ptr artist) { _artist = artist; } void setRelease(Wt::Dbo::ptr release) { _release = release; } - int getTrackNumber(void) const { return _trackNumber; } - int getTotalTrackNumber(void) const { return _totalTrackNumber; } - int getDiscNumber(void) const { return _discNumber; } - int getTotalDiscNumber(void) const { return _totalDiscNumber; } + boost::optional getTrackNumber(void) const; + boost::optional getTotalTrackNumber(void) const; + boost::optional getDiscNumber(void) const; + boost::optional getTotalDiscNumber(void) const; std::string getName(void) const { return _name; } boost::filesystem::path getPath(void) const { return _filePath; } boost::posix_time::time_duration getDuration(void) const { return _duration; } @@ -218,8 +208,6 @@ class Track private: - static Wt::Dbo::Query< pointer > getQuery(Wt::Dbo::Session& session, SearchFilter filter); - static const std::size_t _maxNameLength = 128; int _trackNumber; diff --git a/src/database/Types.hpp b/src/database/Types.hpp index a04b978b..681e4c97 100644 --- a/src/database/Types.hpp +++ b/src/database/Types.hpp @@ -19,7 +19,7 @@ // header file aimed to ease database class declarations -#include "Artist.hpp" +#include "DbArtist.hpp" #include "Track.hpp" #include "Playlist.hpp" #include "Release.hpp" diff --git a/src/ui/ArtistView.cpp b/src/ui/ArtistView.cpp new file mode 100644 index 00000000..5327ced6 --- /dev/null +++ b/src/ui/ArtistView.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2018 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 +#include +#include +#include + +#include "database/Types.hpp" + +#include "utils/Logger.hpp" +#include "utils/Utils.hpp" + +#include "LmsApplication.hpp" +#include "ArtistView.hpp" + +namespace UserInterface { + +Artist::Artist(Filters* filters, Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent), + _filters(filters) +{ + wApp->internalPathChanged().connect(std::bind([=] + { + refresh(); + })); + + refresh(); +} + +void +Artist::refresh() +{ + if (!wApp->internalPathMatches("/artist/")) + return; + + clear(); + + Database::Artist::id_type artistId; + if (!readAs(wApp->internalPathNextPart("/artist/"), artistId)) + return; + + Wt::Dbo::Transaction transaction(DboSession()); + auto artist = Database::Artist::getById(DboSession(), artistId); + + if (!artist) + { + LmsApp->goHome(); + return; + } + + auto t = new Wt::WTemplate(Wt::WString::tr("template-artist"), this); + t->addFunction("tr", &Wt::WTemplate::Functions::tr); + + t->bindString("name", Wt::WString::fromUTF8(artist->getName()), Wt::PlainText); + + auto releasesContainer = new Wt::WContainerWidget(); + t->bindWidget("releases", releasesContainer); + + auto releases = artist->getReleases(_filters->getClusterIds()); + for (auto release : releases) + { + if (release->isNone()) + continue; + + Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-artist-entry"), releasesContainer); + entry->addFunction("tr", Wt::WTemplate::Functions::tr); + + { + Wt::WAnchor *coverAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(release.id()))); + Wt::WImage *cover = new Wt::WImage(coverAnchor); + cover->setImageLink(SessionImageResource()->getReleaseUrl(release.id(), 128)); + // Some images may not be square + cover->setWidth(128); + entry->bindWidget("cover", coverAnchor); + } + + { + Wt::WAnchor *releaseAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(release.id()))); + Wt::WText *releaseName = new Wt::WText(releaseAnchor); + releaseName->setText(Wt::WString::fromUTF8(release->getName(), Wt::PlainText)); + entry->bindWidget("name", releaseAnchor); + } + + if (release->hasVariousArtists()) + entry->setCondition("if-has-various-artists", true); + + boost::optional year = release->getReleaseYear(); + if (year) + { + entry->setCondition("if-has-year", true); + entry->bindInt("year", *year); + + boost::optional originalYear = release->getReleaseYear(true); + if (originalYear && *originalYear != *year) + { + entry->setCondition("if-has-orig-year", true); + entry->bindInt("orig-year", *originalYear); + } + } + } +} + +} // namespace UserInterface + diff --git a/src/ui/audio/mobile/TrackSearchView.hpp b/src/ui/ArtistView.hpp similarity index 77% rename from src/ui/audio/mobile/TrackSearchView.hpp rename to src/ui/ArtistView.hpp index 602d9c56..eb7262d7 100644 --- a/src/ui/audio/mobile/TrackSearchView.hpp +++ b/src/ui/ArtistView.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -20,19 +20,21 @@ #pragma once #include -#include + +#include "Filters.hpp" namespace UserInterface { -namespace Mobile { -class TrackSearchView : public Wt::WContainerWidget +class Artist : public Wt::WContainerWidget { public: + Artist(Filters* filters, Wt::WContainerWidget* parent = 0); - TrackSearchView(PlayQueueEvents& events, Wt::WContainerWidget* parent = 0); + private: + void refresh(); + Filters* _filters; }; -} // namespace Mobile } // namespace UserInterface diff --git a/src/ui/ArtistsView.cpp b/src/ui/ArtistsView.cpp new file mode 100644 index 00000000..e684db2d --- /dev/null +++ b/src/ui/ArtistsView.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018 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 +#include +#include + +#include "database/Types.hpp" + +#include "utils/Logger.hpp" +#include "utils/Utils.hpp" + +#include "LmsApplication.hpp" +#include "ArtistsView.hpp" + +namespace UserInterface { + +using namespace Database; + +Artists::Artists(Filters* filters, Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent), + _filters(filters) +{ + auto artists = new Wt::WTemplate(Wt::WString::tr("template-artists"), this); + artists->addFunction("tr", &Wt::WTemplate::Functions::tr); + + auto search = new Wt::WLineEdit(); + artists->bindWidget("search", search); + search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder")); + search->textInput().connect(std::bind([this, search] + { + auto keywords = splitString(search->text().toUTF8(), " "); + refresh(keywords); + })); + + _artistsContainer = new Wt::WContainerWidget(); + artists->bindWidget("artists", _artistsContainer); + + refresh(); +} + + +void +Artists::refresh(std::vector searchKeywords) +{ + _artistsContainer->clear(); + + auto clusterIds = _filters->getClusterIds(); + + Wt::Dbo::Transaction transaction(DboSession()); + + bool moreResults; + auto artists = Artist::getByFilter(DboSession(), + clusterIds, + searchKeywords, + 0, 40, moreResults); + + for (auto artist : artists) + { + Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-artists-entry"), _artistsContainer); + + entry->bindInt("nb-release", artist->getReleases(clusterIds).size()); + + { + Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(artist.id()))); + Wt::WText *artistText = new Wt::WText(artistAnchor); + artistText->setText(Wt::WString::fromUTF8(artist->getName(), Wt::PlainText)); + entry->bindWidget("name", artistAnchor); + } + } + +} + +} // namespace UserInterface + diff --git a/src/ui/audio/mobile/MobileAudio.hpp b/src/ui/ArtistsView.hpp similarity index 71% rename from src/ui/audio/mobile/MobileAudio.hpp rename to src/ui/ArtistsView.hpp index c49b24ae..2cc29bfc 100644 --- a/src/ui/audio/mobile/MobileAudio.hpp +++ b/src/ui/ArtistsView.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -21,27 +21,21 @@ #include -#include "audio/Audio.hpp" - -#include "MobilePlayQueueEvents.hpp" - +#include "Filters.hpp" namespace UserInterface { -namespace Mobile { -class Audio : public UserInterface::Audio +class Artists : public Wt::WContainerWidget { public: - Audio(Wt::WContainerWidget *parent = 0); - - void search(std::string text); + Artists(Filters* filters, Wt::WContainerWidget* parent = 0); private: + void refresh(std::vector searchKeywords = std::vector()); - PlayQueueEvents _playQueueEvents; - + Filters* _filters; + Wt::WContainerWidget* _artistsContainer; }; -} // namespace Mobile } // namespace UserInterface diff --git a/src/ui/Explore.cpp b/src/ui/Explore.cpp new file mode 100644 index 00000000..46cfc5ca --- /dev/null +++ b/src/ui/Explore.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2018 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 +#include + +#include "utils/Logger.hpp" + +#include "LmsApplication.hpp" + +#include "ArtistsView.hpp" +#include "ArtistView.hpp" +#include "ReleasesView.hpp" +#include "ReleaseView.hpp" +#include "TracksView.hpp" + +#include "Explore.hpp" + +namespace UserInterface { + + +enum IdxRoot +{ + IdxArtists = 0, + IdxArtist, + IdxReleases, + IdxRelease, + IdxTracks, +}; + +static void +handlePathChange(Wt::WStackedWidget* stack) +{ + static const std::map indexes = + { + { "/artists", IdxArtists }, + { "/artist", IdxArtist }, + { "/releases", IdxReleases }, + { "/release", IdxRelease }, + { "/tracks", IdxTracks }, + }; + + LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'"; + + for (auto index : indexes) + { + if (wApp->internalPathMatches(index.first)) + { + stack->setCurrentIndex(index.second); + return; + } + } +} + +Explore::Explore(Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent) +{ + auto container = new Wt::WTemplate(Wt::WString::tr("template-explore"), this); + + auto filters = new Filters(); + container->bindWidget("filters", filters); + + // Contents + Wt::WStackedWidget* stack = new Wt::WStackedWidget(); + container->bindWidget("contents", stack); + + stack->addWidget(new Artists(filters)); + stack->addWidget(new Artist(filters)); + stack->addWidget(new Releases(/*filters*/)); + stack->addWidget(new Release(/*filters*/)); + stack->addWidget(new Tracks(/*filters*/)); + + wApp->internalPathChanged().connect(std::bind([=] + { + handlePathChange(stack); + })); + + handlePathChange(stack); +} +} // namespace UserInterface + diff --git a/src/ui/audio/mobile/ArtistSearchView.hpp b/src/ui/Explore.hpp similarity index 80% rename from src/ui/audio/mobile/ArtistSearchView.hpp rename to src/ui/Explore.hpp index 29971950..ac360a83 100644 --- a/src/ui/audio/mobile/ArtistSearchView.hpp +++ b/src/ui/Explore.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -22,16 +22,12 @@ #include namespace UserInterface { -namespace Mobile { -class ArtistSearchView : public Wt::WContainerWidget +class Explore : public Wt::WContainerWidget { public: - - ArtistSearchView(Wt::WContainerWidget* parent = 0); - + Explore(Wt::WContainerWidget *parent = 0); }; -} // namespace Mobile } // namespace UserInterface diff --git a/src/ui/audio/Audio.hpp b/src/ui/Filters.cpp similarity index 57% rename from src/ui/audio/Audio.hpp rename to src/ui/Filters.cpp index 422b0cca..8dc3f131 100644 --- a/src/ui/audio/Audio.hpp +++ b/src/ui/Filters.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -17,25 +17,29 @@ * along with LMS. If not, see . */ -#ifndef UI_AUDIO_HPP -#define UI_AUDIO_HPP +#include +#include -#include -#include +#include "Filters.hpp" namespace UserInterface { -class Audio : public Wt::WContainerWidget +Filters::Filters(Wt::WContainerWidget *parent) +: Wt::WContainerWidget(parent) { - public: + auto container = new Wt::WTemplate(Wt::WString::tr("template-filters"), this); - Audio(Wt::WContainerWidget *parent) : Wt::WContainerWidget(parent) {} - virtual ~Audio() {} + // Filters + Wt::WPushButton *addFilterBtn = new Wt::WPushButton(Wt::WText::tr("msg-filter-add")); + container->bindWidget("add-filter", addFilterBtn); + + _filters = new Wt::WContainerWidget(); + container->bindWidget("filters", _filters); + +// _filterIds = {108, 419}; +} - virtual void search(std::string text) = 0; -}; } // namespace UserInterface -#endif diff --git a/src/ui/audio/mobile/ArtistView.hpp b/src/ui/Filters.hpp similarity index 68% rename from src/ui/audio/mobile/ArtistView.hpp rename to src/ui/Filters.hpp index 089e6f53..98d2e4c0 100644 --- a/src/ui/audio/mobile/ArtistView.hpp +++ b/src/ui/Filters.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -20,24 +20,25 @@ #pragma once #include -#include #include "database/Types.hpp" -namespace UserInterface { -namespace Mobile { +#include "Filters.hpp" -class ArtistView : public Wt::WContainerWidget +namespace UserInterface { + +class Filters : public Wt::WContainerWidget { public: + Filters(Wt::WContainerWidget *parent = 0); - static Wt::WLink getLink(Database::Artist::id_type id); + std::vector getClusterIds() const { return _filterIds; } - ArtistView(Wt::WContainerWidget *parent = 0); + private: + Wt::WContainerWidget *_filters; + std::vector _filterIds; }; - -} //namespace Mobile -} //namespace UserInterface +} // namespace UserInterface diff --git a/src/ui/audio/mobile/MobilePlayQueueEvents.hpp b/src/ui/HomeView.cpp similarity index 71% rename from src/ui/audio/mobile/MobilePlayQueueEvents.hpp rename to src/ui/HomeView.cpp index 61775642..730f981e 100644 --- a/src/ui/audio/mobile/MobilePlayQueueEvents.hpp +++ b/src/ui/HomeView.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -17,20 +17,22 @@ * along with LMS. If not, see . */ -#pragma once +#include -#include -#include "database/Types.hpp" +#include "utils/Logger.hpp" + +#include "LmsApplication.hpp" + +#include "HomeView.hpp" namespace UserInterface { -namespace Mobile { -struct PlayQueueEvents + +Home::Home(Wt::WContainerWidget *parent) +: Wt::WContainerWidget(parent) { - Wt::Signal trackPlay; - Wt::Signal trackAdd; -}; + addWidget(new Wt::WTemplate(Wt::WString::tr("template-home"))); +} - -} // namespace Mobile } // namespace UserInterface + diff --git a/src/ui/audio/mobile/ReleaseSearchView.hpp b/src/ui/HomeView.hpp similarity index 80% rename from src/ui/audio/mobile/ReleaseSearchView.hpp rename to src/ui/HomeView.hpp index 8427fe68..33f65d5e 100644 --- a/src/ui/audio/mobile/ReleaseSearchView.hpp +++ b/src/ui/HomeView.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -22,16 +22,18 @@ #include namespace UserInterface { -namespace Mobile { -class ReleaseSearchView : public Wt::WContainerWidget +class Home : public Wt::WContainerWidget { public: + Home(Wt::WContainerWidget *parent = 0); - ReleaseSearchView(Wt::WContainerWidget* parent = 0); + private: + + void displayFilter(void); + void displayContents(void); }; -} // namespace Mobile } // namespace UserInterface diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index d68c2f02..063e97be 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -19,28 +19,19 @@ #include #include -#include #include #include #include -#include -#include -#include +#include #include "config/config.h" #include "utils/Logger.hpp" #include "utils/Utils.hpp" -#include "settings/Settings.hpp" -#include "settings/SettingsFirstConnectionFormView.hpp" - #include "auth/LmsAuth.hpp" -#include "audio/desktop/DesktopAudio.hpp" -#include "audio/mobile/MobileAudio.hpp" -#if HAVE_VIDEO -#include "video/VideoWidget.hpp" -#endif -#include "common/LineEdit.hpp" +#include "Explore.hpp" +#include "HomeView.hpp" + #include "LmsApplication.hpp" @@ -48,19 +39,6 @@ namespace skeletons { extern const char *AuthStrings_xml1; } -namespace { - -bool agentIsMobile() -{ - const Wt::WEnvironment& env = Wt::WApplication::instance()->environment(); - return (env.agentIsIEMobile() - || env.agentIsMobileWebKit() - || env.userAgent().find("Mobile") != std::string::npos // Workaround for firefox - || env.userAgent().find("Tablet") != std::string::npos // Workaround for firefox - ); -} -} - namespace UserInterface { Wt::WApplication* @@ -100,7 +78,13 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti useStyleSheet("resources/font-awesome/css/font-awesome.min.css"); // Add a resource bundle + messageResourceBundle().use(appRoot() + "artist"); + messageResourceBundle().use(appRoot() + "artists"); + messageResourceBundle().use(appRoot() + "messages"); messageResourceBundle().use(appRoot() + "templates"); + messageResourceBundle().use(appRoot() + "release"); + messageResourceBundle().use(appRoot() + "releases"); + messageResourceBundle().use(appRoot() + "tracks"); setTitle("LMS"); @@ -114,9 +98,9 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti LMS_LOG(UI, DEBUG) << "Creating root widget. First connection = " << std::boolalpha << firstConnection; // If here is no account in the database, launch the first connection wizard - if (firstConnection) - createFirstConnectionUI(); - else +// if (firstConnection) +// createFirstConnectionUI(); +// else createLmsUI(); } @@ -149,13 +133,19 @@ TranscodeResource* SessionTranscodeResource() return LmsApplication::instance()->getTranscodeResource(); } +void +LmsApplication::goHome() +{ + setInternalPath("/home", true); +} + void LmsApplication::createFirstConnectionUI() { // Hack, use the auth widget builtin strings builtinLocalizedStrings().useBuiltin(skeletons::AuthStrings_xml1); - root()->addWidget( new Settings::FirstConnectionFormView()); +// root()->addWidget( new Settings::FirstConnectionFormView()); } void @@ -164,6 +154,8 @@ LmsApplication::createLmsUI() _imageResource = new ImageResource(_db, root()); _transcodeResource = new TranscodeResource(_db, root()); + handleAuthEvent(); + /* DbHandler().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent); LmsAuth *authWidget = new LmsAuth(); @@ -174,11 +166,53 @@ LmsApplication::createLmsUI() authWidget->processEnvironment(); root()->addWidget(authWidget); + */ +} + + +enum IdxRoot +{ + IdxHome = 0, + IdxExplore, + IdxPlaylist, + IdxSettings, +}; + + + +static void +handlePathChange(Wt::WStackedWidget* stack) +{ + static const std::map indexes = + { + { "/home", IdxHome }, + { "/artists", IdxExplore }, + { "/artist", IdxExplore }, + { "/releases", IdxExplore }, + { "/release", IdxExplore }, + { "/tracks", IdxExplore }, + { "/playlist", IdxPlaylist }, + { "/settings", IdxSettings }, + }; + + LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'"; + + for (auto index : indexes) + { + if (wApp->internalPathMatches(index.first)) + { + stack->setCurrentIndex(index.second); + return; + } + } + + wApp->setInternalPath("/home", true); } void LmsApplication::handleAuthEvent(void) { + /* if (!DbHandler().getLogin().loggedIn()) { LMS_LOG(UI, INFO) << "User logged out, session = " << Wt::WApplication::instance()->sessionId(); @@ -190,76 +224,61 @@ LmsApplication::handleAuthEvent(void) } LMS_LOG(UI, INFO) << "User '" << CurrentAuthUser().identity(Wt::Auth::Identity::LoginName) << "' logged in from '" << Wt::WApplication::instance()->environment().clientAddress() << "', user agent = " << Wt::WApplication::instance()->environment().agent() << ", session = " << Wt::WApplication::instance()->sessionId(); +*/ + setConfirmCloseMessage(Wt::WString::tr("msg-quit-confirm")); - this->root()->setOverflow(Wt::WContainerWidget::OverflowHidden); - setConfirmCloseMessage("Closing LMS. Are you sure?"); + auto main = new Wt::WTemplate(Wt::WString::tr("template-main"), root()); - // Create a Vertical layout: top is the nav bar, bottom is the contents - Wt::WVBoxLayout *layout = new Wt::WVBoxLayout(this->root()); + // Navbar + auto navbar = new Wt::WNavigationBar(); + navbar->setTitle("LMS", Wt::WLink(Wt::WLink::InternalPath, "/home")); + navbar->setResponsive(true); +// navbar->setStyleClass("main-nav"); - Wt::WNavigationBar *navigation = new Wt::WNavigationBar(); - navigation->setTitle("LMS", "https://github.com/epoupon/lms"); - navigation->setResponsive(true); - navigation->addStyleClass("main-nav"); + main->bindWidget("navbar-top", navbar); - Wt::WStackedWidget *contentsStack = new Wt::WStackedWidget(); - - contentsStack->setOverflow(Wt::WContainerWidget::OverflowAuto); - contentsStack->addStyleClass("contents"); - - Wt::WMenu *menu = new Wt::WMenu(contentsStack); - navigation->addMenu(menu, Wt::AlignRight); - - LineEdit *searchEdit = new LineEdit(500); - navigation->bindWidget("search", searchEdit); - searchEdit->setEmptyText("Search..."); - searchEdit->addStyleClass("navbar-form navbar-nav"); - searchEdit->setWidth(150); - // TODO add a span with a search icon - - menu->setInternalPathEnabled(); - menu->setInternalBasePath("/"); - - Audio *audio; - if (agentIsMobile()) - audio = new Mobile::Audio(); - else - audio = new Desktop::Audio(); - - menu->addItem("Audio", audio)->setPathComponent("audio");; -#if defined HAVE_VIDEO - menu->addItem("Video", new VideoWidget())->setPathComponent("video"); -#endif - menu->addItem("Settings", new Settings::Settings())->setPathComponent("settings"); - - Wt::WPopupMenu *popup = new Wt::WPopupMenu(); - popup->addItem("Logout"); - - Wt::WMenuItem *item = new Wt::WMenuItem( CurrentAuthUser().identity(Wt::Auth::Identity::LoginName) ); - item->setMenu(popup); - menu->addItem(item); - - popup->itemSelected().connect(std::bind([=] (Wt::WMenuItem* item) + auto menu = new Wt::WMenu(); { - if (item && item->text() == "Logout") - DbHandler().getLogin().logout(); - }, std::placeholders::_1)); - - searchEdit->timedChanged().connect(std::bind([=] () + auto menuItem = menu->insertItem(0, Wt::WString::tr("msg-artists")); + menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/artists")); + menuItem->setSelectable(false); + } { - // TODO: check which view is activated and search into it - audio->search(searchEdit->text().toUTF8()); + auto menuItem = menu->insertItem(1, Wt::WString::tr("msg-releases")); + menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/releases")); + menuItem->setSelectable(false); + } + { + auto menuItem = menu->insertItem(2, Wt::WString::tr("msg-tracks")); + menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/tracks")); + menuItem->setSelectable(false); + } + { + auto menuItem = menu->insertItem(3, Wt::WString::tr("msg-playlist")); + menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/playlist")); + menuItem->setSelectable(false); + } + navbar->addMenu(menu); + + // Contents + Wt::WStackedWidget* mainStack = new Wt::WStackedWidget(); + main->bindWidget("contents", mainStack); + + mainStack->addWidget(new Home()); + mainStack->addWidget(new Explore()); + mainStack->addWidget(new Wt::WText("PLAYLIST")); + mainStack->addWidget(new Wt::WText("SETTINGS")); + + // MediaPlayer +// auto player = new AudioPlayer(AudioPlayer::ControlPlayqueue); +// main->bindWidget("player", player); + + internalPathChanged().connect(std::bind([=] + { + handlePathChange(mainStack); })); - layout->addWidget(navigation); - layout->addWidget(contentsStack, 1); - layout->setContentsMargins(0, 0, 0, 0); - - if (agentIsMobile()) - setInternalPath("/audio/search/preview", true); - else - setInternalPath("/audio"); - + handlePathChange(mainStack); } } // namespace UserInterface diff --git a/src/ui/LmsApplication.hpp b/src/ui/LmsApplication.hpp index ee0cfe52..de460e4e 100644 --- a/src/ui/LmsApplication.hpp +++ b/src/ui/LmsApplication.hpp @@ -44,6 +44,9 @@ class LmsApplication : public Wt::WApplication TranscodeResource* getTranscodeResource() { return _transcodeResource; } Database::Handler& getDbHandler() { return _db;} + // Utils + void goHome(); + private: void handleAuthEvent(void); @@ -67,6 +70,7 @@ Database::User::pointer CurrentUser(); ImageResource *SessionImageResource(); TranscodeResource *SessionTranscodeResource(); + } // namespace UserInterface #endif diff --git a/src/ui/ReleaseView.cpp b/src/ui/ReleaseView.cpp new file mode 100644 index 00000000..098b12a5 --- /dev/null +++ b/src/ui/ReleaseView.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2018 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 +#include +#include +#include + +#include "database/Types.hpp" + +#include "utils/Logger.hpp" +#include "utils/Utils.hpp" + +#include "LmsApplication.hpp" +#include "ReleaseView.hpp" + +namespace UserInterface { + +Release::Release(Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent) +{ + wApp->internalPathChanged().connect(std::bind([=] + { + refresh(); + })); + + refresh(); +} + +void +Release::refresh() +{ + if (!wApp->internalPathMatches("/release/")) + return; + + clear(); + + Database::Release::id_type releaseId; + + if (!readAs(wApp->internalPathNextPart("/release/"), releaseId)) + return; + + Wt::Dbo::Transaction transaction(DboSession()); + + auto release = Database::Release::getById(DboSession(), releaseId); + if (!release) + { + LmsApp->goHome(); + return; + } + + auto t = new Wt::WTemplate(Wt::WString::tr("template-release"), this); + t->addFunction("tr", &Wt::WTemplate::Functions::tr); + + t->bindString("name", Wt::WString::fromUTF8(release->getName()), Wt::PlainText); + + boost::optional year = release->getReleaseYear(); + if (year) + { + t->setCondition("if-has-year", true); + t->bindInt("year", *year); + + boost::optional originalYear = release->getReleaseYear(true); + if (originalYear && *originalYear != *year) + { + t->setCondition("if-has-orig-year", true); + t->bindInt("orig-year", *originalYear); + } + } + + { + auto artists = release->getArtists(); + if (artists.size() > 1) + { + t->bindString("artist-name", Wt::WString::tr("msg-various-artists")); + } + else + { + Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(artists.front().id()))); + Wt::WText *artist = new Wt::WText(artistAnchor); + artist->setText(Wt::WString::fromUTF8(artists.front()->getName(), Wt::PlainText)); + t->bindWidget("artist-name", artistAnchor); + } + } + + Wt::WImage *cover = new Wt::WImage(); + cover->setImageLink(Wt::WLink (SessionImageResource()->getReleaseUrl(release.id(), 512))); + t->bindWidget("cover", cover); + + // TODO play/add button + + auto tracksContainer = new Wt::WContainerWidget(); + t->bindWidget("tracks", tracksContainer); + + std::vector clusterIds; // TODO fill + +// auto tracks = release->getTracks(clusterIds); + auto tracks = release->getTracks(); + bool variousArtists = release->hasVariousArtists(); + + for (auto track : tracks) + { + Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-release-entry"), tracksContainer); + + entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); + + if (variousArtists && !track->getArtist()->isNone()) + { + entry->setCondition("if-has-artist", true); + Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(track->getArtist().id()))); + Wt::WText *artistText = new Wt::WText(artistAnchor); + artistText->setText(Wt::WString::fromUTF8(track->getArtist()->getName(), Wt::PlainText)); + entry->bindWidget("artist-name", artistAnchor); + } + + auto trackNumber = track->getTrackNumber(); + if (trackNumber) + { + entry->setCondition("if-has-track-number", true); + entry->bindInt("track-number", *trackNumber); + } + + auto discNumber = track->getDiscNumber(); + auto totalDiscNumber = track->getTotalDiscNumber(); + if (discNumber && totalDiscNumber && *totalDiscNumber > 1) + { + entry->setCondition("if-has-disc-number", true); + entry->bindInt("disc-number", *discNumber); + } + } +} + +} // namespace UserInterface + diff --git a/src/ui/settings/Settings.hpp b/src/ui/ReleaseView.hpp similarity index 81% rename from src/ui/settings/Settings.hpp rename to src/ui/ReleaseView.hpp index 30ffa0b8..0a68a9e9 100644 --- a/src/ui/settings/Settings.hpp +++ b/src/ui/ReleaseView.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -17,18 +17,20 @@ * along with LMS. If not, see . */ +#pragma once + #include namespace UserInterface { -namespace Settings { -class Settings : public Wt::WContainerWidget +class Release : public Wt::WContainerWidget { public: - Settings(Wt::WContainerWidget* parent = 0); + Release(Wt::WContainerWidget* parent = 0); private: + void refresh(); }; -} // namespace Settings } // namespace UserInterface + diff --git a/src/ui/ReleasesView.cpp b/src/ui/ReleasesView.cpp new file mode 100644 index 00000000..7c7da4de --- /dev/null +++ b/src/ui/ReleasesView.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2018 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 +#include +#include +#include +#include + +#include "database/Types.hpp" + +#include "utils/Logger.hpp" +#include "utils/Utils.hpp" + +#include "LmsApplication.hpp" +#include "ReleasesView.hpp" + +namespace UserInterface { + +using namespace Database; + +Releases::Releases(Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent) +{ + auto releases = new Wt::WTemplate(Wt::WString::tr("template-releases"), this); + releases->addFunction("tr", &Wt::WTemplate::Functions::tr); + + auto search = new Wt::WLineEdit(); + releases->bindWidget("search", search); + search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder")); + search->textInput().connect(std::bind([this, search] + { + auto keywords = splitString(search->text().toUTF8(), " "); + refresh(keywords); + })); + + _releasesContainer = new Wt::WContainerWidget(); + releases->bindWidget("releases", _releasesContainer); + + refresh(); +} + + +void +Releases::refresh(std::vector searchKeywords) +{ + _releasesContainer->clear(); + + std::vector clusterIds; // TODO fill + + Wt::Dbo::Transaction transaction(DboSession()); + + bool moreResults; + auto releases = Release::getByFilter(DboSession(), clusterIds, searchKeywords, 0, 40, moreResults); + + for (auto release : releases) + { + Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-releases-entry"), _releasesContainer); + entry->addFunction("tr", Wt::WTemplate::Functions::tr); + + Wt::WAnchor *coverAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(release.id()))); + Wt::WImage *cover = new Wt::WImage(coverAnchor); + cover->setImageLink(SessionImageResource()->getReleaseUrl(release.id(), 128)); + // Some images may not be square + cover->setWidth(128); + entry->bindWidget("cover", coverAnchor); + + entry->bindString("release-name", Wt::WString::fromUTF8(release->getName()), Wt::PlainText); + + auto artists = release->getArtists(); + if (artists.size() > 1) + { + entry->bindString("artist-name", Wt::WString::tr("msg-various-artists")); + } + else + { + Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(artists.front().id()))); + Wt::WText *artist = new Wt::WText(artistAnchor); + artist->setText(Wt::WString::fromUTF8(artists.front()->getName(), Wt::PlainText)); + entry->bindWidget("artist-name", artistAnchor); + } + } +} + +} // namespace UserInterface + diff --git a/src/ui/ReleasesView.hpp b/src/ui/ReleasesView.hpp new file mode 100644 index 00000000..7a4325cd --- /dev/null +++ b/src/ui/ReleasesView.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018 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 . + */ + +#pragma once + +#include + +namespace UserInterface { + +class Releases : public Wt::WContainerWidget +{ + public: + Releases(Wt::WContainerWidget* parent = 0); + + private: + void refresh(std::vector searchKeywords = std::vector()); + + Wt::WContainerWidget *_releasesContainer; +}; + +} // namespace UserInterface + diff --git a/src/ui/TracksView.cpp b/src/ui/TracksView.cpp new file mode 100644 index 00000000..bc834de5 --- /dev/null +++ b/src/ui/TracksView.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2018 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 +#include +#include +#include + +#include "database/Types.hpp" + +#include "utils/Logger.hpp" +#include "utils/Utils.hpp" + +#include "LmsApplication.hpp" +#include "TracksView.hpp" + +namespace UserInterface { + +using namespace Database; + +Tracks::Tracks(Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent) +{ + auto tracks = new Wt::WTemplate(Wt::WString::tr("template-tracks"), this); + tracks->addFunction("tr", &Wt::WTemplate::Functions::tr); + + auto search = new Wt::WLineEdit(); + tracks->bindWidget("search", search); + search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder")); + search->textInput().connect(std::bind([this, search] + { + auto keywords = splitString(search->text().toUTF8(), " "); + refresh(keywords); + })); + + _tracksContainer = new Wt::WContainerWidget(); + tracks->bindWidget("tracks", _tracksContainer); + + refresh(); +} + + +void +Tracks::refresh(std::vector searchKeywords) +{ + _tracksContainer->clear(); + + std::vector clusterIds; // TODO fill + + Wt::Dbo::Transaction transaction(DboSession()); + + bool moreResults; + auto tracks = Track::getByFilter(DboSession(), clusterIds, searchKeywords, 0, 40, moreResults); + + for (auto track : tracks) + { + Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-tracks-entry"), _tracksContainer); + + entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); + + auto artist = track->getArtist(); + if (!artist->isNone()) + { + entry->setCondition("if-has-artist", true); + Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(track->getArtist().id()))); + Wt::WText *artistText = new Wt::WText(artistAnchor); + artistText->setText(Wt::WString::fromUTF8(artist->getName(), Wt::PlainText)); + entry->bindWidget("artist-name", artistAnchor); + } + + auto release = track->getRelease(); + if (!release->isNone()) + { + entry->setCondition("if-has-release", true); + // TODO anchor + Wt::WAnchor *releaseAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(track->getRelease().id()))); + Wt::WText *releaseText = new Wt::WText(releaseAnchor); + releaseText->setText(Wt::WString::fromUTF8(release->getName(), Wt::PlainText)); + entry->bindWidget("release-name", releaseAnchor); + } + + Wt::WImage *cover = new Wt::WImage(); + cover->setImageLink(SessionImageResource()->getTrackUrl(track.id(), 64)); + // Some images may not be square + cover->setWidth(64); + entry->bindWidget("cover", cover); + } + +} + +} // namespace UserInterface + diff --git a/src/ui/audio/mobile/PreviewSearchView.hpp b/src/ui/TracksView.hpp similarity index 74% rename from src/ui/audio/mobile/PreviewSearchView.hpp rename to src/ui/TracksView.hpp index fab0b4e9..7ba2c61c 100644 --- a/src/ui/audio/mobile/PreviewSearchView.hpp +++ b/src/ui/TracksView.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -21,19 +21,18 @@ #include -#include "MobilePlayQueueEvents.hpp" - namespace UserInterface { -namespace Mobile { -class PreviewSearchView : public Wt::WContainerWidget +class Tracks : public Wt::WContainerWidget { public: + Tracks(Wt::WContainerWidget* parent = 0); - PreviewSearchView(PlayQueueEvents& events, Wt::WContainerWidget* parent = 0); + private: + void refresh(std::vector searchKeywords = std::vector()); + Wt::WContainerWidget *_tracksContainer; }; -} // namespace Mobile } // namespace UserInterface diff --git a/src/ui/audio/AudioPlayer.cpp b/src/ui/audio/AudioPlayer.cpp deleted file mode 100644 index bb487af9..00000000 --- a/src/ui/audio/AudioPlayer.cpp +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright (C) 2015 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 -#include -#include -#include - -#include "common/InputRange.hpp" - -#include "utils/Logger.hpp" -#include "utils/Utils.hpp" - -#include "LmsApplication.hpp" - -#include "AudioPlayer.hpp" - -namespace UserInterface { - - -Av::Encoding -AudioPlayer::getBestEncoding() const -{ - // TODO get the supported formats of the player and pick one - return Av::Encoding::MP3; -} - -bool -AudioPlayer::loadTrack(Database::Track::id_type trackId) -{ - // TODO reduce this scope - Wt::Dbo::Transaction transaction(DboSession()); - - Database::Track::pointer track = Database::Track::getById(DboSession(), trackId); - if (!track) - { - LMS_LOG(UI, INFO) << "No track found for id " << trackId; - return false; - } - - Database::User::pointer user = CurrentUser(); - if (!user) - { - LMS_LOG(UI, ERROR) << "No user found!"; - return false; - } - - Av::Encoding encoding; - switch (user->getAudioEncoding()) - { - case Database::AudioEncoding::MP3: encoding = Av::Encoding::MP3; break; - case Database::AudioEncoding::OGA: encoding = Av::Encoding::OGA; break; - case Database::AudioEncoding::WEBMA: encoding = Av::Encoding::WEBMA; break; - case Database::AudioEncoding::AUTO: - default: - encoding = getBestEncoding(); - break; - } - - bindString("track", Wt::WString::fromUTF8(track->getName())); - bindString("artist", Wt::WString::fromUTF8(track->getArtist()->getName())); - _cover->setImageLink(SessionImageResource()->getTrackUrl(trackId, 64)); - - std::string durationFormat = track->getDuration().total_seconds() < 3600 ? "%M:%S" : "%H:%M:%S"; - - _trackDuration->setText(durationToString(track->getDuration(), durationFormat)); - _trackCurTime->setText(durationToString(boost::posix_time::seconds(0), durationFormat)); - - // Analyse track, select the best media stream - Av::MediaFile mediaFile(track->getPath()); - - if (!mediaFile.open() || !mediaFile.scan()) - { - LMS_LOG(UI, ERROR) << "Cannot open file '" << track->getPath(); - return false; - } - - - int audioBestStreamId = mediaFile.getBestStreamId(Av::Stream::Type::Audio); - std::vector streams; - if (audioBestStreamId != -1) - streams.push_back(audioBestStreamId); - - this->doJavaScript("\ - document.lms.audio.state = \"loaded\";\ - document.lms.audio.seekbar.min = " + std::to_string(0) + ";\ - document.lms.audio.seekbar.max = " + std::to_string(track->getDuration().total_seconds()) + ";\ - document.lms.audio.seekbar.value = 0;\ - document.lms.audio.seekbar.disabled = false;\ - document.lms.audio.offset = 0;\ - document.lms.audio.curTime = 0;\ - "); - - LMS_LOG(UI, DEBUG) << "Loading, URL = '" << SessionTranscodeResource()->getUrl(trackId, encoding, 0, streams) << "'"; - - //TODO, try to load everything in JS in order to prevent the WriteError bug? - _audio->pause(); - _audio->clearSources(); - _audio->addSource(SessionTranscodeResource()->getUrl(trackId, encoding, 0, streams)); - _audio->setPreloadMode(Wt::WAudio::PreloadNone); - _audio->play(); - - return true; -} - -AudioPlayer::AudioPlayer(ControlFlags controls, Wt::WContainerWidget *parent) -: Wt::WTemplate(parent) -{ - setTemplateText(Wt::WString::tr("wa-audio-player")); - addStyleClass("mediaplayer"); - - // TODO potential leak here - _audio = new Wt::WAudio(); - _audio->setOptions(Wt::WAudio::Autoplay); - _audio->setPreloadMode(Wt::WAudio::PreloadNone); - bindWidget("audio", _audio); - - _audio->ended().connect(std::bind([=] () - { - _playbackEnded.emit(); - })); - - _cover = new Wt::WImage(); - bindWidget("cover", _cover); - _cover->setImageLink(SessionImageResource()->getUnknownTrackUrl(64)); - - InputRange *seekbar = new InputRange(); - seekbar->addStyleClass("mediaplayer-seekbar"); - bindWidget("seekbar", seekbar); - - bindString("track", "Track"); - bindString("artist", "Artist"); - - InputRange *volumeSlider = new InputRange(); - volumeSlider->addStyleClass("mediaplayer-volume"); - volumeSlider->setAttributeValue("orient", "vertical"); // firefox - bindWidget("volume", volumeSlider); - - if (controls & ControlRepeat) - { - setCondition("if-has-repeat", true); - Wt::WText *repeatBtn = new Wt::WText("", Wt::XHTMLText); - repeatBtn->addStyleClass("mediaplayer-btn hidden-xs"); - bindWidget("repeat", repeatBtn); - repeatBtn->clicked().connect(std::bind([=] () - { - if (repeatBtn->hasStyleClass("mediaplayer-btn-active")) - { - repeatBtn->removeStyleClass("mediaplayer-btn-active"); - _loop.emit(false); - } - else - { - repeatBtn->addStyleClass("mediaplayer-btn-active"); - _loop.emit(true); - } - })); - } - - if (controls & ControlPlayqueue) - { - setCondition("if-has-playqueue", true); - Wt::WText *playQueueBtn = new Wt::WText("", Wt::XHTMLText); - bindWidget("playqueue", playQueueBtn); - playQueueBtn->addStyleClass("mediaplayer-btn"); - playQueueBtn->clicked().connect(std::bind([=] () - { - _playQueue.emit(); - })); - } - - if (controls & ControlShuffle) - { - setCondition("if-has-shuffle", true); - Wt::WText *shuffleBtn = new Wt::WText("", Wt::XHTMLText); - shuffleBtn->addStyleClass("mediaplayer-btn hidden-xs"); - bindWidget("shuffle", shuffleBtn); - shuffleBtn->clicked().connect(std::bind([=] () - { - if (shuffleBtn->hasStyleClass("mediaplayer-btn-active")) - { - shuffleBtn->removeStyleClass("mediaplayer-btn-active"); - _shuffle.emit(false); - } - else - { - shuffleBtn->addStyleClass("mediaplayer-btn-active"); - _shuffle.emit(true); - } - })); - } - - Wt::WText *prevBtn = new Wt::WText("", Wt::XHTMLText); - prevBtn->addStyleClass("mediaplayer-btn"); - bindWidget("prev", prevBtn); - prevBtn->clicked().connect(std::bind([=] () - { - _playPrevious.emit(); - })); - - Wt::WText *nextBtn = new Wt::WText("", Wt::XHTMLText); - nextBtn->addStyleClass("mediaplayer-btn"); - bindWidget("next", nextBtn); - nextBtn->clicked().connect(std::bind([=] () - { - _playNext.emit(); - })); - - Wt::WText *playPauseBtn = new Wt::WText("", Wt::XHTMLText); - playPauseBtn->addStyleClass("mediaplayer-btn"); - bindWidget("play-pause", playPauseBtn); - - _trackCurTime = new Wt::WText("--:--"); - _trackCurTime->addStyleClass("hidden-xs badge mediaplayer-badge mediaplayer-current-duration"); - bindWidget("curtime", _trackCurTime); - - _trackDuration = new Wt::WText("--:--"); - _trackDuration->addStyleClass("hidden-xs badge mediaplayer-badge mediaplayer-total-duration"); - bindWidget("duration", _trackDuration); - - this->doJavaScript( - "\ - document.lms = {};\ - document.lms.audio = {};\ - document.lms.audio.audio = " + _audio->jsRef() + ";\ - document.lms.audio.seekbar = " + seekbar->jsRef() +";\ - document.lms.audio.volumeSlider = " + volumeSlider->jsRef() + ";\ - document.lms.audio.curTimeText = " + _trackCurTime->jsRef() + ";\ - document.lms.audio.playPause = " + playPauseBtn->jsRef() + ";\ - \ - document.lms.audio.offset = 0;\ - document.lms.audio.curTime = 0;\ - document.lms.audio.state = \"init\";\ - document.lms.audio.volume = 1;\ - \ - document.lms.audio.seekbar.value = 0;\ - document.lms.audio.seekbar.disabled = true;\ - \ - document.lms.audio.volumeSlider.min = 0;\ - document.lms.audio.volumeSlider.max = 100;\ - document.lms.audio.volumeSlider.value = 100;\ - \ - function updateUIPlaying() { \ - var icon = document.lms.audio.playPause.getElementsByTagName(\"i\")[0]; \ - icon.className = \"fa fa-pause fa-3x fa-fw\"; \ - } \ - \ - function updateUIStopped() { \ - var icon = document.lms.audio.playPause.getElementsByTagName(\"i\")[0]; \ - icon.className = \"fa fa-play fa-3x fa-fw\"; \ - } \ - \ - function durationToString(duration, displayHours) { \ - var hours = parseInt( duration / 3600 ) % 24; \ - var minutes = parseInt( duration / 60) % 60; \ - var seconds = duration % 60; \ - \ - var res = \"\"; \ - if (displayHours) \ - res = (hours < 10 ? \"0\" + hours : hours) + \":\"; \ - \ - res += (minutes < 10 ? \"0\" + minutes : minutes) + \":\"; \ - res += (seconds < 10 ? \"0\" + seconds : seconds); \ - return res; \ - } \ - \ - function updateUI() {\ - document.lms.audio.curTimeText.innerHTML = durationToString(document.lms.audio.curTime, document.lms.audio.seekbar.max > 3600); \ - document.lms.audio.seekbar.value = document.lms.audio.curTime;\ - }\ - \ - var mouseDown = 0;\ - function seekMouseDown(e) {\ - ++mouseDown;\ - }\ - function seekMouseUp(e) {\ - --mouseDown;\ - }\ - \ - function seeking(e) {\ - if (document.lms.audio.state == \"init\")\ - return;\ - \ - document.lms.audio.curTimeText.innerHTML = durationToString(document.lms.audio.seekbar.value, document.lms.audio.seekbar.max > 3600); \ - }\ - \ - function seek(e) {\ - if (document.lms.audio.state == \"init\")\ - return;\ - \ - document.lms.audio.audio.pause(); \ - document.lms.audio.offset = parseInt(document.lms.audio.seekbar.value);\ - document.lms.audio.curTime = document.lms.audio.seekbar.value;\ - var audioSource = document.lms.audio.audio.getElementsByTagName(\"source\")[0];\ - var src = audioSource.src;\ - src = src.slice(0, src.lastIndexOf(\"=\") + 1);\ - audioSource.src = src + document.lms.audio.seekbar.value;\ - document.lms.audio.audio.load(); \ - document.lms.audio.audio.play(); \ - document.lms.audio.curTimeText.innerHTML = durationToString(document.lms.audio.curTime, document.lms.audio.seekbar.max > 3600); \ - }\ - \ - function volumeChanged() {\ - document.lms.audio.audio.volume = document.lms.audio.volumeSlider.value / 100;\ - }\ - \ - function updateCurTime() {\ - document.lms.audio.curTime = document.lms.audio.offset + ~~document.lms.audio.audio.currentTime; \ - if (mouseDown == 0)\ - updateUI();\ - } \ - \ - function playPause() { \ - if (document.lms.audio.state == \"init\") \ - return;\ - \ - if (document.lms.audio.audio.paused) \ - document.lms.audio.audio.play(); \ - else \ - document.lms.audio.audio.pause();\ - \ - }\ - \ - document.lms.audio.audio.addEventListener('timeupdate', updateCurTime); \ - document.lms.audio.audio.addEventListener('playing', updateUIPlaying); \ - document.lms.audio.audio.addEventListener('play', updateUIPlaying); \ - document.lms.audio.audio.addEventListener('pause', updateUIStopped); \ - document.lms.audio.audio.addEventListener('ended', updateUIStopped); \ - document.lms.audio.seekbar.addEventListener('change', seek);\ - document.lms.audio.seekbar.addEventListener('input', seeking);\ - document.lms.audio.seekbar.addEventListener('mousedown', seekMouseDown);\ - document.lms.audio.seekbar.addEventListener('mouseup', seekMouseUp);\ - document.lms.audio.volumeSlider.addEventListener('input', volumeChanged);\ - document.lms.audio.playPause.addEventListener('click', playPause);\ - " - ); -} - - -} // namespaceUserInterface diff --git a/src/ui/audio/AudioPlayer.hpp b/src/ui/audio/AudioPlayer.hpp deleted file mode 100644 index 669091e7..00000000 --- a/src/ui/audio/AudioPlayer.hpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#pragma once - -#include -#include - -#include "common/InputRange.hpp" -#include "database/Types.hpp" -#include "av/AvTranscoder.hpp" - -namespace UserInterface { - -class AudioPlayer : public Wt::WTemplate -{ - public: - - enum ControlFlags - { - ControlNone = 0, - ControlShuffle = 1 << 0, - ControlRepeat = 1 << 1, - ControlPlayqueue = 1 << 2, - }; - - AudioPlayer(ControlFlags controls, Wt::WContainerWidget *parent = 0); - - Av::Encoding getBestEncoding() const; - bool loadTrack(Database::Track::id_type trackId); - - // Signals - Wt::Signal& playbackEnded() {return _playbackEnded;} - Wt::Signal& playNext() {return _playNext;} - Wt::Signal& playPrevious() {return _playPrevious;} - Wt::Signal& shuffle() {return _shuffle;} - Wt::Signal& loop() {return _loop;} - Wt::Signal& showPlayQueue() {return _playQueue;} - - private: - - // Signals - Wt::Signal _playbackEnded; - Wt::Signal _playNext; - Wt::Signal _playPrevious; - Wt::Signal _shuffle; - Wt::Signal _loop; - Wt::Signal _playQueue; - - Wt::WAudio* _audio; - Wt::WText* _trackCurTime; - Wt::WText* _trackDuration; - Wt::WText* _trackName; - Wt::WImage* _cover; -}; - -constexpr AudioPlayer::ControlFlags operator|(AudioPlayer::ControlFlags f1, AudioPlayer::ControlFlags f2) { return AudioPlayer::ControlFlags(int(f1)|int(f2)); } - - -} // namespace UserInterface diff --git a/src/ui/audio/desktop/DesktopAudio.cpp b/src/ui/audio/desktop/DesktopAudio.cpp deleted file mode 100644 index 63e73c53..00000000 --- a/src/ui/audio/desktop/DesktopAudio.cpp +++ /dev/null @@ -1,556 +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 -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "utils/Logger.hpp" - -#include "LmsApplication.hpp" - -#include "TableFilter.hpp" -#include "KeywordSearchFilter.hpp" - -#include "DesktopAudio.hpp" - -namespace { - -void WPopupMenuClear(Wt::WPopupMenu* menu) -{ - while(menu->count() > 0) - menu->removeItem(menu->itemAt(0)); -} - -} - -namespace UserInterface { -namespace Desktop { - -using namespace Database; - -// Special playlist generated each time the playque gets changed -// Restored at the beginning of the session -static const std::string CurrentQueuePlaylistName = "__current__"; - -Audio::Audio(Wt::WContainerWidget* parent) -: UserInterface::Audio(parent), -_mediaPlayer(nullptr), -_trackView(nullptr), -_playQueue(nullptr) -{ - Wt::WGridLayout *mainLayout = new Wt::WGridLayout(); - this->setLayout(mainLayout); - mainLayout->setContentsMargins(9,4,9,9); - - // Filters - Wt::WHBoxLayout *filterLayout = new Wt::WHBoxLayout(); - - TableFilterCluster *filterCluster = new TableFilterCluster(); - filterLayout->addWidget(filterCluster); - _filterChain.addFilter(filterCluster); - - TableFilterArtist *filterArtist = new TableFilterArtist(); - filterLayout->addWidget(filterArtist); - _filterChain.addFilter(filterArtist); - - TableFilterRelease *filterRelease = new TableFilterRelease(); - filterLayout->addWidget(filterRelease); - _filterChain.addFilter(filterRelease); - - mainLayout->addLayout(filterLayout, 0, 1); - // ENDOF(Filters) - - // TODO ADD some stats (nb files, total duration, etc.) - - Wt::WVBoxLayout* trackLayout = new Wt::WVBoxLayout(); - - _trackView = new TrackView(); - trackLayout->addWidget(_trackView, 1); - - Wt::WHBoxLayout* trackControls = new Wt::WHBoxLayout(); - - Wt::WPushButton* playBtn = new Wt::WPushButton("Play"); - playBtn->setStyleClass("btn-sm"); - trackControls->addWidget(playBtn); - - Wt::WPushButton* addBtn = new Wt::WPushButton("Add"); - addBtn->setStyleClass("btn-sm"); - trackControls->addWidget(addBtn); - - Wt::WText *statsText = new Wt::WText(); - statsText->setStyleClass("vertical-align"); - trackControls->addWidget(statsText, 1); - _trackView->statsUpdated().connect(std::bind([=] (Wt::WString stats) { - statsText->setText(stats); - }, std::placeholders::_1)); - - trackLayout->addLayout(trackControls); - - mainLayout->addLayout(trackLayout, 1, 1); - - _filterChain.addFilter(_trackView); - - - _playQueue = new PlayQueue(); - - // Playlist/PlayQueue - Wt::WContainerWidget* playQueueContainer = new Wt::WContainerWidget(); - playQueueContainer->setStyleClass("playqueue"); - Wt::WVBoxLayout* playQueueLayout = new Wt::WVBoxLayout(); - playQueueContainer->setLayout(playQueueLayout); - - playQueueLayout->addWidget( _playQueue, 1); - - Wt::WHBoxLayout* playlistControls = new Wt::WHBoxLayout(); - - Wt::WPushButton *playlistBtn = new Wt::WPushButton("Playlist"); - playlistBtn->setStyleClass("btn-sm btn-primary"); - playlistControls->addWidget(playlistBtn); - - // Playlist menu - { - Wt::WPopupMenu *popupMain = new Wt::WPopupMenu(); - - _popupMenuSave = new Wt::WPopupMenu(); - popupMain->addMenu("Save", _popupMenuSave); - - _popupMenuLoad = new Wt::WPopupMenu(); - popupMain->addMenu("Load", _popupMenuLoad); - - _popupMenuDelete = new Wt::WPopupMenu(); - popupMain->addMenu("Delete", _popupMenuDelete); - - playlistBtn->setMenu(popupMain); - } - -#if WT_VERSION >= 0X03030400 - Wt::WPushButton *upBtn = new Wt::WPushButton("", Wt::XHTMLText); -#else - Wt::WPushButton *upBtn = new Wt::WPushButton("UP"); -#endif - upBtn->setStyleClass("btn-sm"); - playlistControls->addWidget(upBtn); - -#if WT_VERSION >= 0X03030400 - Wt::WPushButton *downBtn = new Wt::WPushButton("", Wt::XHTMLText); -#else - Wt::WPushButton *downBtn = new Wt::WPushButton("DOWN"); -#endif - downBtn->setStyleClass("btn-sm"); - playlistControls->addWidget(downBtn); -#if WT_VERSION >= 0X03030400 - Wt::WPushButton *delBtn = new Wt::WPushButton("", Wt::XHTMLText); -#else - Wt::WPushButton *delBtn = new Wt::WPushButton("DEL"); -#endif - - delBtn->setStyleClass("btn-sm btn-warning"); - playlistControls->addWidget(delBtn); -#if WT_VERSION >= 0X03030400 - Wt::WPushButton *clearBtn = new Wt::WPushButton("", Wt::XHTMLText); -#else - Wt::WPushButton *clearBtn = new Wt::WPushButton("CLR"); -#endif - clearBtn->setStyleClass("btn-sm btn-danger"); - playlistControls->addWidget(clearBtn); - - delBtn->clicked().connect(_playQueue, &PlayQueue::delSelected); - upBtn->clicked().connect(_playQueue, &PlayQueue::moveSelectedUp); - downBtn->clicked().connect(_playQueue, &PlayQueue::moveSelectedDown); - clearBtn->clicked().connect(std::bind([=] - { - Wt::WMessageBox *messageBox = new Wt::WMessageBox - ("Clear playqueue", - Wt::WString( "Are you sure?"), - Wt::Question, Wt::Yes | Wt::No); - - messageBox->setModal(true); - - messageBox->buttonClicked().connect(std::bind([=] () - { - if (messageBox->buttonResult() == Wt::Yes) - _playQueue->delAll(); - - delete messageBox; - })); - - messageBox->show(); - })); - - playQueueLayout->addLayout(playlistControls); - - mainLayout->addWidget(playQueueContainer, 0, 0, 2, 1); - - // Load the last known queue - playlistLoadToPlayqueue(CurrentQueuePlaylistName); - - // Select the last known playing track - { - Wt::Dbo::Transaction transaction(DboSession()); - _playQueue->select(CurrentUser()->getCurPlayingTrackPos()); - } - - _mediaPlayer = new AudioPlayer(AudioPlayer::ControlShuffle | AudioPlayer::ControlRepeat); - mainLayout->addWidget(_mediaPlayer, 2, 0, 1, 4); - - mainLayout->setRowStretch(1, 1); - mainLayout->setRowResizable(0, true, Wt::WLength(250, Wt::WLength::Pixel)); - mainLayout->setColumnResizable(0, true, Wt::WLength(400, Wt::WLength::Pixel)); - - // Double click on track - // Set the selected tracks to the play queue - _trackView->trackDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddSelectedTracks)); - - // 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)); - filterCluster->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks)); - - // Play button - // Set the selected tracks to the play queue - 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(this, &Audio::addSelectedTracks); - - _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->shuffle().connect(boost::bind(&PlayQueue::setShuffle, _playQueue, _1)); - _mediaPlayer->loop().connect(boost::bind(&PlayQueue::setLoop,_playQueue, _1)); - - _playQueue->tracksUpdated().connect(std::bind([=] () { - LMS_LOG(UI, INFO) << "Playqueue updated!"; - - playlistSaveFromPlayqueue(CurrentQueuePlaylistName); - })); - - - playlistRefreshMenus(); - - // Initially, search for everything - _filterChain.searchKeyword(""); -} - -void -Audio::playlistShowSaveNewDialog() -{ - Wt::WDialog *dialog = new Wt::WDialog("New playlist"); - - Wt::WLabel *label = new Wt::WLabel("Name", dialog->contents()); - Wt::WLineEdit *edit = new Wt::WLineEdit(dialog->contents()); - label->setBuddy(edit); - - Wt::WLengthValidator* validator = new Wt::WLengthValidator(); - validator->setMinimumLength(3); - validator->setMandatory(true); - edit->setValidator(validator); - - Wt::WPushButton *save = new Wt::WPushButton("Save", dialog->footer()); - save->setStyleClass("btn-success"); - save->setDefault(true); - save->disable(); - - Wt::WPushButton *cancel = new Wt::WPushButton("Cancel", dialog->footer()); - dialog->rejectWhenEscapePressed(); - - edit->keyWentUp().connect(std::bind([=] () { - save->setDisabled(edit->validate() != Wt::WValidator::Valid); - })); - - save->clicked().connect(std::bind([=] () - { - if (edit->validate()) - dialog->accept(); - })); - - cancel->clicked().connect(dialog, &Wt::WDialog::reject); - - dialog->finished().connect(std::bind([=] () - { - if (dialog->result() == Wt::WDialog::Accepted) - { - playlistShowSaveDialog(edit->text().toUTF8()); - } - - delete dialog; - })); - - dialog->show(); -} - -void -Audio::playlistShowSaveDialog(std::string playlistName) -{ - Wt::Dbo::Transaction transaction(DboSession()); - - // Actually create the dialog only if the given list already exists - if (Playlist::get(DboSession(), playlistName, CurrentUser())) - { - Wt::WMessageBox *messageBox = new Wt::WMessageBox - ("Overwrite playlist", - Wt::WString( "Overwrite playlist '{1}'?").arg(playlistName), - Wt::Question, Wt::Yes | Wt::No); - - messageBox->setModal(true); - - messageBox->buttonClicked().connect(std::bind([=] () { - if (messageBox->buttonResult() == Wt::Yes) - playlistSaveFromPlayqueue(playlistName); - - delete messageBox; - })); - - messageBox->show(); - } - else - { - playlistSaveFromPlayqueue(playlistName); - playlistRefreshMenus(); - } -} - -void -Audio::playlistSaveFromPlayqueue(std::string playlistName) -{ - LMS_LOG(UI, INFO) << "Saving playqueue to playlist '" << playlistName << "'"; - - Wt::Dbo::Transaction transaction(DboSession()); - - Playlist::pointer playlist = Playlist::get(DboSession(), playlistName, CurrentUser()); - if (playlist) - { - LMS_LOG(UI, INFO) << "Erasing playlist '" << playlistName << "'"; - playlist.remove(); - } - - playlist = Playlist::create(DboSession(), playlistName, false, CurrentUser()); - - std::vector trackIds; - _playQueue->getTracks(trackIds); - - int pos = 0; - for (Track::id_type trackId : trackIds) - { - Track::pointer track = Track::getById(DboSession(), trackId); - - if (track) - PlaylistEntry::create(DboSession(), track, playlist, pos++); - } - - LMS_LOG(UI, INFO) << "Saving playqueue to playlist '" << playlistName << "' done. Contains " << pos << " entries"; -} - -void -Audio::playlistLoadToPlayqueue(std::string playlistName) -{ - LMS_LOG(UI, DEBUG) << "Loading playlist '" << playlistName << "' to playqueue"; - - std::vector entries; - - { - Wt::Dbo::Transaction transaction(DboSession()); - - Playlist::pointer playlist = Playlist::get(DboSession(), playlistName, CurrentUser()); - if (!playlist) - return; - - entries = PlaylistEntry::getEntries(DboSession(), playlist); - } - - _playQueue->clear(); - _playQueue->addTracks(entries); - - LMS_LOG(UI, DEBUG) << "Loading playlist '" << playlistName << "' to playqueue done. " << entries.size() << " entries"; -} - - -void -Audio::playlistShowDeleteDialog(std::string name) -{ - - Wt::WMessageBox *messageBox = new Wt::WMessageBox - ("Delete playlist", - Wt::WString( "Deleting playlist '{1}'?").arg(name), - Wt::Question, Wt::Yes | Wt::No); - - messageBox->setModal(true); - - messageBox->buttonClicked().connect(std::bind([=] () { - if (messageBox->buttonResult() == Wt::Yes) - { - Wt::Dbo::Transaction transaction(DboSession()); - - Playlist::pointer playlist = Playlist::get(DboSession(), name, CurrentUser()); - if (playlist) - playlist.remove(); - - playlistRefreshMenus(); - } - - delete messageBox; - })); - - messageBox->show(); -} - -void -Audio::playlistRefreshMenus() -{ - Wt::Dbo::Transaction transaction(DboSession()); - - // Clear playlists in each menu - LMS_LOG(UI, DEBUG) << "Save item count: " << _popupMenuSave->count(); - - WPopupMenuClear(_popupMenuDelete); - WPopupMenuClear(_popupMenuLoad); - WPopupMenuClear(_popupMenuSave); - - _popupMenuSave->addItem("New")->triggered().connect(std::bind([=] () - { - playlistShowSaveNewDialog(); - })); - _popupMenuSave->addSeparator(); - - std::vector playlists = Playlist::get(DboSession(), CurrentUser()); - - for (Playlist::pointer playlist : playlists) - { - if (playlist->getName() == CurrentQueuePlaylistName) - continue; - - // Add playlists in each menu - _popupMenuDelete->addItem(playlist->getName())->triggered().connect(std::bind([=] () - { - playlistShowDeleteDialog(playlist->getName()); - })); - - _popupMenuLoad->addItem(playlist->getName())->triggered().connect(std::bind([=] () - { - playlistLoadToPlayqueue(playlist->getName()); - playlistRefreshMenus(); // in case deleted in other session - })); - - _popupMenuSave->addItem(playlist->getName())->triggered().connect(std::bind([=] () - { - playlistShowSaveDialog(playlist->getName()); - })); - } -} - - -void -Audio::search(std::string searchText) -{ - _filterChain.searchKeyword(searchText); -} - -void -Audio::addSelectedTracks(void) -{ - std::vector trackIds; - _trackView->getSelectedTracks(trackIds); - - // If nothing is selected, get the whole track list - if (trackIds.empty()) - _trackView->getTracks(trackIds); - - _playQueue->addTracks(trackIds); -} - -void -Audio::playSelectedTracks(PlayQueueAddType addType) -{ - std::vector trackIds; - - LMS_LOG(UI, DEBUG) << "Playing selected tracks... nb selected = " << _trackView->getNbSelectedTracks() << ", add type = " << (addType == PlayQueueAddAllTracks ? "AddAll" : "AddSelected"); - - _playQueue->clear(); - - switch(addType) - { - case PlayQueueAddAllTracks: - // Play all the tracks - _trackView->getTracks(trackIds); - _playQueue->addTracks(trackIds); - _playQueue->play(); - - break; - case PlayQueueAddSelectedTracks: - - LMS_LOG(UI, DEBUG) << "Adding selected tracks..."; - - // If nothing selected, get all the track and play everything - if (_trackView->getNbSelectedTracks() == 0) - { - _trackView->getTracks(trackIds); - _playQueue->addTracks(trackIds); - _playQueue->play(); - } - // If only ONE selected, get them all and pre select the track - else if (_trackView->getNbSelectedTracks() == 1) - { - // Start to play at the selected track, if any - _trackView->getTracks(trackIds); - _playQueue->addTracks(trackIds); - _playQueue->play(_trackView->getFirstSelectedTrackPosition()); - } - else - { - // Play all the selected tracks - _trackView->getSelectedTracks(trackIds); - _playQueue->addTracks(trackIds); - _playQueue->play(); - } - break; - } -} - -void -Audio::playTrack(Track::id_type trackId, int pos) -{ - { - Wt::Dbo::Transaction transaction(DboSession()); - - // Update user current track position - CurrentUser().modify()->setCurPlayingTrackPos(pos); - } - - if (!_mediaPlayer->loadTrack(trackId)) - _playQueue->playNext(); -} - -} // namespace Desktop -} // namespace UserInterface - diff --git a/src/ui/audio/desktop/DesktopAudio.hpp b/src/ui/audio/desktop/DesktopAudio.hpp deleted file mode 100644 index ac7bda65..00000000 --- a/src/ui/audio/desktop/DesktopAudio.hpp +++ /dev/null @@ -1,81 +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 . - */ - -#pragma once - -#include - -#include - -#include "audio/AudioPlayer.hpp" -#include "TrackView.hpp" -#include "PlayQueue.hpp" - -#include "FilterChain.hpp" - -#include "audio/Audio.hpp" - -namespace UserInterface { -namespace Desktop { - -class Audio : public UserInterface::Audio -{ - - public: - - Audio(Wt::WContainerWidget* parent = 0); - - void search(std::string searchText); - - private: - - void playlistSaveFromPlayqueue(std::string name); - void playlistLoadToPlayqueue(std::string name); - void playlistShowSaveNewDialog(); - void playlistShowSaveDialog(std::string name); - void playlistShowDeleteDialog(std::string name); - void playlistRefreshMenus(); - - void playTrack(Database::Track::id_type id, int pos); - - enum PlayQueueAddType - { - PlayQueueAddAllTracks, - PlayQueueAddSelectedTracks, - }; - - void playSelectedTracks(PlayQueueAddType addType); - void addSelectedTracks(); - - void handlePlaylistSelected(Wt::WString name); - - AudioPlayer* _mediaPlayer; - TrackView* _trackView; - PlayQueue* _playQueue; - - FilterChain _filterChain; - - Wt::WPopupMenu* _popupMenuSave; - Wt::WPopupMenu* _popupMenuLoad; - Wt::WPopupMenu* _popupMenuDelete; -}; - -} // namespace Desktop -} // namespace UserInterface - diff --git a/src/ui/audio/desktop/Filter.hpp b/src/ui/audio/desktop/Filter.hpp deleted file mode 100644 index 5feb120a..00000000 --- a/src/ui/audio/desktop/Filter.hpp +++ /dev/null @@ -1,57 +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 FILTER_HPP -#define FILTER_HPP - -#include - -#include "database/Types.hpp" - -namespace UserInterface { -namespace Desktop { - -class Filter -{ - public: - Filter() {} - virtual ~Filter() {} - - // Refresh filter using constraints created by parent filters - virtual void refresh(Database::SearchFilter& filter) = 0; - - // Update constraints for next filters - virtual void getConstraint(Database::SearchFilter& filter) = 0; - - // Emitted when a constraint has changed - Wt::Signal& update() { return _update; }; - - protected: - - void emitUpdate() { _update.emit(); } - - private: - - Wt::Signal _update; -}; - -} // namespace Dekstop -} // namespace UserInterface - -#endif diff --git a/src/ui/audio/desktop/FilterChain.cpp b/src/ui/audio/desktop/FilterChain.cpp deleted file mode 100644 index d94ddea5..00000000 --- a/src/ui/audio/desktop/FilterChain.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 { -namespace Desktop { - - -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; - - Database::SearchFilter searchFilter; - - 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(searchFilter); - } - - // Get constraints generated by this filter - // (Note: adding accross successive calls) - filter->getConstraint(searchFilter); - } - - _refreshingFilters = false; -} - -} // namespace Desktop -} // namespace UserInterface - diff --git a/src/ui/audio/desktop/FilterChain.hpp b/src/ui/audio/desktop/FilterChain.hpp deleted file mode 100644 index c9625941..00000000 --- a/src/ui/audio/desktop/FilterChain.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 { -namespace Desktop { - -// 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 Desktop -} // namespace UserInterface - -#endif diff --git a/src/ui/audio/desktop/KeywordSearchFilter.cpp b/src/ui/audio/desktop/KeywordSearchFilter.cpp deleted file mode 100644 index 3749960a..00000000 --- a/src/ui/audio/desktop/KeywordSearchFilter.cpp +++ /dev/null @@ -1,64 +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 -#include - -#include "KeywordSearchFilter.hpp" - -namespace UserInterface { -namespace Desktop { - -KeywordSearchFilter::KeywordSearchFilter() -{ -} - -void -KeywordSearchFilter::setText(const std::string& text) -{ - _lastEmittedText = text; - emitUpdate(); -} - -// Get constraints created by this filter -void -KeywordSearchFilter::getConstraint(Database::SearchFilter& filter) -{ - // No active search means no constaint! - if (!_lastEmittedText.empty()) { - std::vector values; - boost::algorithm::split(values, _lastEmittedText, boost::is_any_of(" "), boost::token_compress_on); - - // For each part, do a global search on all searchable fields - for (const std::string& value : values) - { - filter.nameLikeMatch.push_back({ - {Database::SearchFilter::Field::Artist, {value}}, - {Database::SearchFilter::Field::Release, {value}}, - {Database::SearchFilter::Field::Cluster, {value}}, - {Database::SearchFilter::Field::Track, {value}} - }); - } - } -} - -} // namespace Desktop -} // namespace UserInterface - diff --git a/src/ui/audio/desktop/KeywordSearchFilter.hpp b/src/ui/audio/desktop/KeywordSearchFilter.hpp deleted file mode 100644 index acf62b0b..00000000 --- a/src/ui/audio/desktop/KeywordSearchFilter.hpp +++ /dev/null @@ -1,54 +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 KEYWORD_SEARCH_FILTER_HPP -#define KEYWORD_SEARCH_FILTER_HPP - -#include - -#include "Filter.hpp" - -namespace UserInterface { -namespace Desktop { - -class KeywordSearchFilter : public Filter -{ - public: - KeywordSearchFilter(); - - void setText(const std::string& text); - - // Set constraint on this filter - void refresh(Database::SearchFilter& filter) {} - - // Get constraints created by this filter - void getConstraint(Database::SearchFilter& filter); - - private: - - void handleKeyWentUp(void); - - std::string _lastEmittedText; -}; - -} // namespace Desktop -} // namespace UserInterface - -#endif - diff --git a/src/ui/audio/desktop/PlayQueue.cpp b/src/ui/audio/desktop/PlayQueue.cpp deleted file mode 100644 index 8eea4231..00000000 --- a/src/ui/audio/desktop/PlayQueue.cpp +++ /dev/null @@ -1,634 +0,0 @@ -/* - * 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 -#include - -#include -#include -#include -#include -#include -#include - -#include "utils/Logger.hpp" - -#include "LmsApplication.hpp" -#include "PlayQueue.hpp" - -static const int TrackInfoRole = Wt::UserRole; - -namespace { - - void swapRows(Wt::WStandardItemModel *model, int row1, int row2) - { - // Swap data column by column - for (int i = 0; i < model->columnCount(); ++i) - { - Wt::WModelIndex index1 = model->index(row1, i); - Wt::WModelIndex index2 = model->index(row2, i); - - // swap data associated with standard roles - { - auto tmp = model->itemData(index1); // 1 -> tmp - model->setItemData(index1, model->itemData(index2)); // 2 -> 1 - model->setItemData(index2, tmp); // tmp-> 2 - } - // caution: swap data associated with our custom roles if any!! - } - } -} - -namespace UserInterface { -namespace Desktop { - -enum ColumnId -{ - COLUMN_ID_TRACK_ID = 0, - COLUMN_ID_COVER = 1, - COLUMN_ID_NAME = 2, -}; - -static const int trackPosInvalid = -1; - -class TrackSelector -{ - public: - TrackSelector(); - - void setShuffle(bool enable); - void setLoop(bool enable) { _loop = enable; } - - int previous(void); - int next(void); - int getCurrent(void); - - // Set the internal pos thanks to the track pos - void setPosByRowId(int rowId); - void setPos(int pos) { _curPos = pos; } - - void setSize(std::size_t size); - std::size_t getSize(void) const { return _size;} - - private: - - void refreshPositions(); - - bool _loop; - bool _shuffle; - - std::size_t _size; - std::size_t _curPos; - - std::vector _trackPos; -}; - -TrackSelector::TrackSelector() -: _loop(false), -_shuffle(false), -_size(0), -_curPos(0) -{ -} - -void -TrackSelector::refreshPositions() -{ - _trackPos.clear(); - - if (_size == 0) - return; - - for (std::size_t i = 0; i < _size; ++i) - _trackPos.push_back(i); - - // Now shuffle - // Source: http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle - boost::random::mt19937 rng; // produces randomness out of thin air - rng.seed(static_cast(std::time(0))); - - for (std::size_t i = 0; i < _size; ++i) - { - boost::random::uniform_int_distribution<> distribution(i, _size - 1); - int val1 = distribution(rng); - int tmp = _trackPos[i]; - _trackPos[i] = _trackPos[val1]; - _trackPos[val1] = tmp; - } -} - -int -TrackSelector::getCurrent() -{ - return _shuffle ? _trackPos[_curPos] : _curPos; -} - -void -TrackSelector::setShuffle(bool enable) -{ - _shuffle = enable; - - if (_size != 0) - { - // Update the current index we are playing if we switch shuffle - if (_shuffle) - setPosByRowId(_curPos); - else - setPos(_trackPos[_curPos]); - } -} - -int -TrackSelector::next() -{ - if (_size == 0) - return trackPosInvalid; - - if (_curPos == _size - 1) - { - if (!_loop) - return trackPosInvalid; - else - _curPos = 0; - } - else - ++_curPos; - - return _shuffle ? _trackPos[_curPos] : _curPos; -} - -int -TrackSelector::previous() -{ - if (_size == 0) - return trackPosInvalid; - - if (_curPos == 0) - { - if (!_loop) - return trackPosInvalid; - else - _curPos = _size - 1; - } - else - --_curPos; - - return _shuffle ? _trackPos[_curPos] : _curPos; -} - -void -TrackSelector::setPosByRowId(int rowId) -{ - if (_shuffle) - { - for (std::size_t i = 0; i < _trackPos.size(); ++i) - { - if (rowId == _trackPos[i]) - { - _curPos = i; - break; - } - } - } - else - _curPos = rowId; -} - -void -TrackSelector::setSize(std::size_t size) -{ - _size = size; - _curPos = 0; - refreshPositions(); -} - -struct TrackInfo -{ - Wt::WString track; - Wt::WString artist; - Wt::WString release; -}; - -class PlayQueueItemDelegate : public Wt::WItemDelegate -{ - public: - PlayQueueItemDelegate(Wt::WObject *parent = 0) : Wt::WItemDelegate(parent) {} - - Wt::WWidget* update(Wt::WWidget *widget, const Wt::WModelIndex &index, Wt::WFlags< Wt::ViewItemRenderFlag > flags) - { - Wt::WWidget* res; - - if (!index.data(TrackInfoRole).empty()) - { - TrackInfo trackInfo = boost::any_cast(index.data(TrackInfoRole)); - - Wt::WContainerWidget *container = new Wt::WContainerWidget(); - Wt::WText* track = new Wt::WText(trackInfo.track, Wt::PlainText, container); - Wt::WText* artist = new Wt::WText(trackInfo.artist + " - " + trackInfo.release, Wt::PlainText, container); - - artist->setInline(false); - track->setInline(false); - - artist->setStyleClass("playqueue-artist"); - track->setStyleClass("playqueue-track"); - - // Apply style if any - Wt::WString styleClass = Wt::asString(index.data(Wt::StyleClassRole)); - - // Apply selection style if any - if (flags & Wt::RenderSelected) - styleClass += " " + Wt::WApplication::instance()->theme()->activeClass(); - - container->setStyleClass(styleClass); - - res = container; - } - else - res = Wt::WItemDelegate::update(widget, index, flags); - - return res; - } - - private: - -}; - - -PlayQueue::PlayQueue(Wt::WContainerWidget* parent) -: Wt::WTableView(parent), -_curPlayedTrackPos(trackPosInvalid), -_trackSelector(new TrackSelector()) -{ - _model = new Wt::WStandardItemModel(0, 3, this); - - // 0 Column is hidden (track id) - - this->setHeaderHeight(0); - this->setModel(_model); - this->setSelectionMode(Wt::ExtendedSelection); - this->setSortingEnabled(false); - this->setAlternatingRowColors(true); - this->setRowHeight(64); - this->setColumnWidth(COLUMN_ID_COVER, 64); - this->setColumnWidth(COLUMN_ID_NAME, 240); - - this->setLayoutSizeAware(true); -#if WT_VERSION >= 0X03030400 - this->setOverflow(Wt::WContainerWidget::OverflowHidden, Wt::Horizontal); - this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical); -#endif - this->setColumnHidden(COLUMN_ID_TRACK_ID, true); - - _itemDelegate = new PlayQueueItemDelegate(); - this->setItemDelegate(_itemDelegate); - - this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt) - { - if (!idx.isValid()) - return; - - // Update selection - Wt::WModelIndexSet indexSet; - indexSet.insert(idx); - - this->setSelectedIndexes( indexSet ); - - // Read the requested track - play(idx.row()); - - }, std::placeholders::_1, std::placeholders::_2)); - -} - -void -PlayQueue::layoutSizeChanged (int width, int height) -{ - std::size_t coverColumnSize = this->columnWidth(COLUMN_ID_COVER).toPixels(); - // Set the remaining size for the name column - this->setColumnWidth(COLUMN_ID_NAME, width - coverColumnSize - (7 * 2) - 2); -} - -void -PlayQueue::setShuffle(bool enable) -{ - _trackSelector->setShuffle(enable); -} - -void -PlayQueue::setLoop(bool enable) -{ - _trackSelector->setLoop(enable); -} - -void -PlayQueue::play() -{ - _trackSelector->setPos(0); - - readTrack(_trackSelector->getCurrent()); -} - -void -PlayQueue::play(int rowId) -{ - // Update the track selector to use the requested track as current position - _trackSelector->setPosByRowId(rowId); - - readTrack(_trackSelector->getCurrent()); -} - -void -PlayQueue::select(int rowId) -{ - // Update the track selector to use the requested track as current position - _trackSelector->setPosByRowId(rowId); - - Wt::WTableView::select(_model->index(_trackSelector->getCurrent(), 0)); - - this->scrollTo( _model->index(_trackSelector->getCurrent(), 0)); -} - -void -PlayQueue::addTracks(const std::vector& trackIds) -{ - using namespace Database; - - LMS_LOG(UI, DEBUG) << "Adding " << trackIds.size() << " tracks to play queue"; - - // Add tracks to model - for (Track::id_type trackId : trackIds) - { - Wt::Dbo::Transaction transaction(DboSession()); - - Track::pointer track (Track::getById(DboSession(), trackId)); - - if (track) - { - int dataRow = _model->rowCount(); - - _model->insertRows(dataRow, 1); - - _model->setData(dataRow, COLUMN_ID_TRACK_ID, track.id(), Wt::UserRole); - - std::string coverUrl; - coverUrl = SessionImageResource()->getTrackUrl(track.id(), 64); - - _model->setData(dataRow, COLUMN_ID_COVER, coverUrl, Wt::DecorationRole); - _model->setData(dataRow, COLUMN_ID_COVER, std::string("playqueue-cover"), Wt::StyleClassRole); - - TrackInfo trackInfo; - trackInfo.track = Wt::WString::fromUTF8(track->getName()); - trackInfo.artist = Wt::WString::fromUTF8(track->getArtist()->getName()); - trackInfo.release = Wt::WString::fromUTF8(track->getRelease()->getName()); - _model->setData(dataRow, COLUMN_ID_NAME, trackInfo, TrackInfoRole); - } - } - - _trackSelector->setSize( _model->rowCount() ); - - _sigTracksUpdated.emit(); -} - -void -PlayQueue::clear(void) -{ - _model->removeRows(0, _model->rowCount()); - - // Reset play id - _curPlayedTrackPos = trackPosInvalid; - _trackSelector->setSize( 0 ); - - _sigTracksUpdated.emit(); -} - -void -PlayQueue::handlePlaybackComplete(void) -{ - playNext(); -} - -void -PlayQueue::playNext(void) -{ - std::size_t nbTries = _trackSelector->getSize(); - - while (nbTries > 0) - { - int pos = _trackSelector->next(); - if (pos == trackPosInvalid) - break; - - readTrack(pos); - - break; - } -} - -void -PlayQueue::playPrevious(void) -{ - std::size_t nbTries = _trackSelector->getSize(); - - while (nbTries > 0) - { - int pos = _trackSelector->previous(); - if (pos == trackPosInvalid) - break; - - readTrack(pos); - break; - - } -} - -void -PlayQueue::readTrack(int rowPos) -{ - LMS_LOG(UI, DEBUG) << "Reading track at pos " << rowPos << ", row count = " << _model->rowCount(); - - if (rowPos < _model->rowCount()) - { - Database::Track::id_type trackId = boost::any_cast(_model->data(rowPos, COLUMN_ID_TRACK_ID, Wt::UserRole)); - - setPlayingTrackPos(rowPos); - - _sigTrackPlay.emit(trackId, rowPos); - - this->scrollTo( _model->index(_trackSelector->getCurrent(), 0)); - } -} - -void -PlayQueue::setPlayingTrackPos(int newRowPos) -{ - int oldRowPos = _curPlayedTrackPos; - _curPlayedTrackPos = newRowPos; - - // 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 (oldRowPos >= 0) - { - _model->setData(oldRowPos, COLUMN_ID_NAME, boost::any(), Wt::StyleClassRole); - } - - if (newRowPos >= 0) - { - _model->setData(newRowPos, COLUMN_ID_NAME, std::string("playqueue-playing"), Wt::StyleClassRole); - } -} - -void -PlayQueue::delSelected(void) -{ - int minId = _model->rowCount(); - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - - // Make sure to delete entries in the reverse order - std::vector rowIds; - for (Wt::WModelIndex index : indexSet) - rowIds.push_back(index.row()); - - std::sort(rowIds.begin(), rowIds.end(), std::greater()); - if (!rowIds.empty()) - minId = rowIds.back(); - - for (int rowId : rowIds) - _model->removeRow(rowId); - - // If the current played track is removed, make sure to unselect it - _trackSelector->setSize(_model->rowCount()); - - renumber(minId, _model->rowCount() - 1); - _sigTracksUpdated.emit(); -} - -void -PlayQueue::delAll(void) -{ - _model->removeRows(0, _model->rowCount()); - _trackSelector->setSize(0); - _sigTracksUpdated.emit(); -} - -void -PlayQueue::moveSelectedUp(void) -{ - int minId = _model->rowCount(); - int maxId = 0; - - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - Wt::WModelIndexSet newIndexSet; - - // ordered from up to down - for (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 (_curPlayedTrackPos == index.row()) - setPlayingTrackPos(_curPlayedTrackPos - 1); - else if (_curPlayedTrackPos == index.row() - 1) - setPlayingTrackPos(_curPlayedTrackPos + 1); - - newIndexSet.insert( _model->index(index.row() - 1, 0)); - } - - _trackSelector->setPosByRowId(_curPlayedTrackPos); - this->setSelectedIndexes( newIndexSet ); - - renumber(minId, maxId); - - _sigTracksUpdated.emit(); -} - - -void -PlayQueue::moveSelectedDown(void) -{ - int minId = _model->rowCount(); - int maxId = 0; - - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - Wt::WModelIndexSet newIndexSet; - - // ordered from up to down - for (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 (_curPlayedTrackPos == index.row()) - setPlayingTrackPos(_curPlayedTrackPos + 1); - else if (_curPlayedTrackPos == index.row() + 1) - setPlayingTrackPos(_curPlayedTrackPos - 1); - - newIndexSet.insert( _model->index(index.row() + 1, 0)); - } - - _trackSelector->setPosByRowId(_curPlayedTrackPos); - this->setSelectedIndexes( newIndexSet ); - - renumber(minId, maxId); - - _sigTracksUpdated.emit(); -} - -void -PlayQueue::renumber(int firstId, int lastId) -{ - for (int i = firstId; i <= lastId; ++i) - _model->setData( i, 1, i + 1); -} - -void -PlayQueue::getTracks(std::vector& trackIds) const -{ - // Now add each entry in the playlist - for (int i = 0; i < _model->rowCount(); ++i) - { - Database::Track::id_type trackId = boost::any_cast(_model->data(i, COLUMN_ID_TRACK_ID, Wt::UserRole)); - trackIds.push_back(trackId); - } -} - -} // namespace Desktop -} // namespace UserInterface - diff --git a/src/ui/audio/desktop/PlayQueue.hpp b/src/ui/audio/desktop/PlayQueue.hpp deleted file mode 100644 index 0f0ca22d..00000000 --- a/src/ui/audio/desktop/PlayQueue.hpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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 PLAY_QUEUE_HPP -#define PLAY_QUEUE_HPP - -#include -#include - -#include "database/Types.hpp" - -namespace UserInterface { -namespace Desktop { - -class PlayQueueItemDelegate; -class TrackSelector; - -class PlayQueue : public Wt::WTableView -{ - public: - PlayQueue(Wt::WContainerWidget* parent = 0); - - void addTracks(const std::vector& trackIds); - void getTracks(std::vector& trackIds) const; - - void clear(void); - - void setShuffle(bool enable); // true to enable shuffle - void setLoop(bool enable); // true to enable loop - - // Play functions - void play(void); // Play the queue from the beginning - void play(int rowId); // Play the queue from the given rowId - void select(int rowId); // Select the given rowId - void playNext(void); // Play the next track - void playPrevious(void); // Play the previous track - - // List manipulations - void delSelected(void); - void delAll(void); - void moveSelectedUp(void); - void moveSelectedDown(void); - - // Signals - // Emitted when a song has to be played - Wt::Signal< Database::Track::id_type, int >& playTrack() { return _sigTrackPlay; } - // Emitted when the list has changed - Wt::Signal< void >& tracksUpdated() { return _sigTracksUpdated; } - - // Slots - void handlePlaybackComplete(void); - - private: - - void layoutSizeChanged (int width, int height); - - void readTrack(int rowId); - void setPlayingTrackPos(int newRowPos); - void renumber(int firstId, int lastId); - - Wt::Signal< Database::Track::id_type, int > _sigTrackPlay; - Wt::Signal< void > _sigTracksUpdated; - - Wt::WStandardItemModel* _model; - - PlayQueueItemDelegate* _itemDelegate; - - int _curPlayedTrackPos; - std::unique_ptr _trackSelector; - -}; - -} // namespace Desktop -} // namespace UserInterface - -#endif diff --git a/src/ui/audio/desktop/TableFilter.cpp b/src/ui/audio/desktop/TableFilter.cpp deleted file mode 100644 index 6af45e97..00000000 --- a/src/ui/audio/desktop/TableFilter.cpp +++ /dev/null @@ -1,282 +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 - -#include "database/Types.hpp" - -#include "utils/Logger.hpp" - -#include "LmsApplication.hpp" -#include "TableFilter.hpp" - -namespace UserInterface { -namespace Desktop { - -using namespace Database; - -TableFilterCluster::TableFilterCluster(Wt::WContainerWidget* parent) -: Wt::WTableView( parent ), Filter() -{ - const std::vector columnNames = {"Tag", "Tracks"}; - - SearchFilter filter; - - Cluster::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames); - - this->setSelectionMode(Wt::ExtendedSelection); - this->setSortingEnabled(true); - this->setAlternatingRowColors(true); - this->setModel(&_queryModel); - - this->setColumnWidth(1, 80); - - this->selectionChanged().connect(this, &TableFilterCluster::emitUpdate); - -#if WT_VERSION >= 0X03030400 - this->setOverflow(Wt::WContainerWidget::OverflowHidden, Wt::Horizontal); - this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical); -#endif - - this->setLayoutSizeAware(true); - - _queryModel.setBatchSize(150); - - // 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 -TableFilterCluster::layoutSizeChanged (int width, int height) -{ - std::size_t trackColumnSize = this->columnWidth(1).toPixels(); - // Set the remaining size for the name column - this->setColumnWidth(0, width - trackColumnSize - (7 * 2) - 2); -} - -// Set constraints on this filter -void -TableFilterCluster::refresh(SearchFilter& filter) -{ -#if WT_VERSION >= 0X03030500 - this->clearSelection(); -#endif - - Cluster::updateUIQueryModel(DboSession(), _queryModel, filter); -} - -// Get constraint created by this filter -void -TableFilterCluster::getConstraint(SearchFilter& filter) -{ - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - - for (Wt::WModelIndex index : indexSet) - { - if (!index.isValid()) - continue; - - // TODO set invisible track id - Cluster::id_type id = _queryModel.resultRow( index.row() ).get<0>(); - - filter.idMatch[Database::SearchFilter::Field::Cluster].push_back(id); - } -} - -TableFilterArtist::TableFilterArtist(Wt::WContainerWidget* parent) -: Wt::WTableView( parent ), Filter() -{ - const std::vector columnNames = {"Artist", "Releases", "Tracks"}; - - SearchFilter filter; - - Artist::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames); - - this->setSelectionMode(Wt::ExtendedSelection); - this->setSortingEnabled(true); - this->setAlternatingRowColors(true); - this->setModel(&_queryModel); - - this->setColumnWidth(1, 80); - this->setColumnWidth(2, 80); - - this->selectionChanged().connect(this, &TableFilterArtist::emitUpdate); - -#if WT_VERSION >= 0X03030400 - this->setOverflow(Wt::WContainerWidget::OverflowHidden, Wt::Horizontal); - this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical); -#endif - - this->setLayoutSizeAware(true); - - _queryModel.setBatchSize(150); - - // 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 -TableFilterArtist::layoutSizeChanged (int width, int height) -{ - std::size_t otherColumnsSize = this->columnWidth(1).toPixels() + this->columnWidth(2).toPixels(); - // Set the remaining size for the name column - this->setColumnWidth(0, width - otherColumnsSize - (7 * 3) - 2); -} - -// Set constraints on this filter -void -TableFilterArtist::refresh(SearchFilter& filter) -{ -#if WT_VERSION >= 0X03030500 - this->clearSelection(); -#endif - Artist::updateUIQueryModel(DboSession(), _queryModel, filter); -} - -// Get constraint created by this filter -void -TableFilterArtist::getConstraint(SearchFilter& filter) -{ - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - - for (Wt::WModelIndex index : indexSet) - { - if (!index.isValid()) - continue; - - Artist::id_type id = _queryModel.resultRow( index.row() ).get<0>(); - - filter.idMatch[SearchFilter::Field::Artist].push_back(id); - } -} - -TableFilterRelease::TableFilterRelease(Wt::WContainerWidget* parent) -: Wt::WTableView( parent ), Filter() -{ - const std::vector columnNames = {"Release", "Date", "Tracks"}; - - SearchFilter filter; - - Release::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames); - - this->setSelectionMode(Wt::ExtendedSelection); - this->setSortingEnabled(true); - this->setAlternatingRowColors(true); - this->setModel(&_queryModel); - - this->setColumnWidth(1, 60); - this->setColumnWidth(2, 80); - - // Date display, just the year - { - Wt::WItemDelegate *delegate = new Wt::WItemDelegate(this); - delegate->setTextFormat("yyyy"); - this->setItemDelegateForColumn(1, delegate); - } - - this->selectionChanged().connect(this, &TableFilterRelease::emitUpdate); - -#if WT_VERSION >= 0X03030400 - this->setOverflow(Wt::WContainerWidget::OverflowHidden, Wt::Horizontal); - this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical); -#endif - - this->setLayoutSizeAware(true); - - _queryModel.setBatchSize(150); - - // 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 -TableFilterRelease::layoutSizeChanged (int width, int height) -{ - std::size_t otherColumnSizes = this->columnWidth(1).toPixels() + this->columnWidth(2).toPixels(); - // Set the remaining size for the name column - this->setColumnWidth(0, width - otherColumnSizes - (7 * 3) - 2); -} - -// Set constraints on this filter -void -TableFilterRelease::refresh(SearchFilter& filter) -{ -#if WT_VERSION >= 0X03030500 - this->clearSelection(); -#endif - Release::updateUIQueryModel(DboSession(), _queryModel, filter); -} - -// Get constraint created by this filter -void -TableFilterRelease::getConstraint(SearchFilter& filter) -{ - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - - for (Wt::WModelIndex index : indexSet) - { - if (!index.isValid()) - continue; - - Release::id_type id = _queryModel.resultRow( index.row() ).get<0>(); - - filter.idMatch[Database::SearchFilter::Field::Release].push_back(id); - } -} - -} // namespace Desktop -} // namespace UserInterface - diff --git a/src/ui/audio/desktop/TableFilter.hpp b/src/ui/audio/desktop/TableFilter.hpp deleted file mode 100644 index 243b17f2..00000000 --- a/src/ui/audio/desktop/TableFilter.hpp +++ /dev/null @@ -1,112 +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 TABLE_FILTER_HPP -#define TABLE_FILTER_HPP - - -#include -#include - -#include "Filter.hpp" -#include "database/Types.hpp" - -namespace UserInterface { -namespace Desktop { - -class TableFilterCluster : public Wt::WTableView, public Filter -{ - public: - TableFilterCluster(Wt::WContainerWidget* parent = 0); - - // Set constraints on this filter - void refresh(Database::SearchFilter& filter); - - // Get constraints created by this filter - void getConstraint(Database::SearchFilter& filter); - - void layoutSizeChanged (int width, int height); - - typedef Wt::Signal SigDoubleClicked; - - SigDoubleClicked& sigDoubleClicked() { return _sigDoubleClicked; } - - protected: - - SigDoubleClicked _sigDoubleClicked; - - Wt::Dbo::QueryModel _queryModel; -}; - -class TableFilterArtist : public Wt::WTableView, public Filter -{ - public: - TableFilterArtist(Wt::WContainerWidget* parent = 0); - - // Set constraints on this filter - void refresh(Database::SearchFilter& filter); - - // Get constraints created by this filter - void getConstraint(Database::SearchFilter& filter); - - void layoutSizeChanged (int width, int height); - - typedef Wt::Signal SigDoubleClicked; - - SigDoubleClicked& sigDoubleClicked() { return _sigDoubleClicked; } - - protected: - - SigDoubleClicked _sigDoubleClicked; - - // Name, track count - Wt::Dbo::QueryModel _queryModel; -}; - -class TableFilterRelease : public Wt::WTableView, public Filter -{ - public: - TableFilterRelease(Wt::WContainerWidget* parent = 0); - - // Set constraints on this filter - void refresh(Database::SearchFilter& filter); - - // Get constraints created by this filter - void getConstraint(Database::SearchFilter& filter); - - void layoutSizeChanged (int width, int height); - - typedef Wt::Signal SigDoubleClicked; - - SigDoubleClicked& sigDoubleClicked() { return _sigDoubleClicked; } - - protected: - - SigDoubleClicked _sigDoubleClicked; - - // Name, track count - Wt::Dbo::QueryModel _queryModel; -}; - - -} // namespace Desktop -} // namespace UserInterface - -#endif - diff --git a/src/ui/audio/desktop/TrackView.cpp b/src/ui/audio/desktop/TrackView.cpp deleted file mode 100644 index aaec7939..00000000 --- a/src/ui/audio/desktop/TrackView.cpp +++ /dev/null @@ -1,234 +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 -#include -#include - -#include "utils/Logger.hpp" -#include "utils/Utils.hpp" - -#include "LmsApplication.hpp" -#include "TrackView.hpp" - -namespace UserInterface { -namespace Desktop { - -using namespace Database; - -class DurationItemDelegate : public Wt::WItemDelegate -{ - public: - DurationItemDelegate(Wt::WObject *parent = 0) : Wt::WItemDelegate(parent) {} - - Wt::WWidget* update(Wt::WWidget *widget, const Wt::WModelIndex &index, Wt::WFlags< Wt::ViewItemRenderFlag > flags) - { - boost::posix_time::time_duration duration = boost::any_cast(index.data(Wt::DisplayRole)); - - // TODO bug when redrawn - std::string format = duration.total_seconds() < 3600 ? "%M:%S" : "%H:%M:%S"; - - return new Wt::WText(durationToString(duration, format), Wt::PlainText); - } -}; - -TrackView::TrackView(Wt::WContainerWidget* parent) -: Wt::WTableView( parent ) -{ - - static const std::vector columnNames = - { - "Artist", - "Album", - "Disc #", - "Track #", - "Track", - "Duration", - "Date", - "Original Date", - "Genres", - }; - - SearchFilter filter; - - Track::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames); - - _queryModel.setBatchSize(500); - - this->setSortingEnabled(true); - this->setSelectionMode(Wt::ExtendedSelection); - this->setAlternatingRowColors(true); - this->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 -#if WT_VERSION >= 0X03030400 - this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical); -#endif - - // Duration display - { - // TODO better handle 1 hour+ files! - DurationItemDelegate *delegate = new DurationItemDelegate(this); - this->setItemDelegateForColumn(5, delegate); - } - - // Date display, just the year - { - Wt::WItemDelegate *delegate = new Wt::WItemDelegate(this); - delegate->setTextFormat("yyyy"); - this->setItemDelegateForColumn(6, delegate); - } - { - Wt::WItemDelegate *delegate = new Wt::WItemDelegate(this); - delegate->setTextFormat("yyyy"); - this->setItemDelegateForColumn(7, delegate); - } - - // If an item is double clicked, select the track 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 ); - - _sigTrackDoubleClicked.emit( ); - - }, std::placeholders::_1, std::placeholders::_2)); - -} - -void -TrackView::emitStats(const SearchFilter& filter) -{ - Wt::Dbo::Transaction transaction (DboSession()); - - // Update stats on the view - Track::StatsQueryResult stats = Track::getStats(DboSession(), filter); - - transaction.commit(); - - int nbTracks = stats.get<0>(); - boost::posix_time::time_duration totalDuration = stats.get<1>(); - - std::ostringstream oss; - - oss << nbTracks << " track" << (nbTracks > 1 ? "s" : "") << ", "; - - if (totalDuration.hours() >= 24) - { - auto days = totalDuration.hours() / 24; - oss << days << " day" << (days > 1 ? "s " : " "); - } - - oss << std::setw(2) << std::setfill('0') << totalDuration.hours() % 24 - << ":" << std::setw(2) << std::setfill('0') << totalDuration.minutes() - << ":" << std::setw(2) << std::setfill('0') << totalDuration.seconds(); - - _sigStatsUpdated.emit(Wt::WString(oss.str())); -} - -// Set constraints created by parent filters -void -TrackView::refresh(SearchFilter& filter) -{ -#if WT_VERSION >= 0X03030500 - this->clearSelection(); -#endif - Track::updateUIQueryModel(DboSession(), _queryModel, filter); - - emitStats(filter); -} - -void -TrackView::getSelectedTracks(std::vector& track_ids) -{ - LMS_LOG(UI, DEBUG) << "Getting selected tracks..."; - - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - - for (Wt::WModelIndex index : indexSet) - { - if (!index.isValid()) - continue; - - Track::id_type id = _queryModel.resultRow( index.row() ).get<0>(); - - track_ids.push_back(id); - } - - LMS_LOG(UI, DEBUG) << "Getting all selected tracks: " << track_ids.size(); -} - -std::size_t -TrackView::getNbSelectedTracks(void) -{ - return this->selectedIndexes().size(); -} - -int -TrackView::getFirstSelectedTrackPosition(void) -{ - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - - for (Wt::WModelIndex index : indexSet) - { - if (!index.isValid()) - continue; - - return index.row(); - } - - return 0; -} - -void -TrackView::getTracks(std::vector& trackIds) -{ - LMS_LOG(UI, DEBUG) << "Getting all tracks..."; - - Wt::Dbo::Transaction transaction(DboSession()); - Wt::Dbo::Query query = _queryModel.query(); - Wt::Dbo::collection results = query.limit(-1).offset(-1); - - for (auto it = results.begin(); it != results.end(); ++it) - { - Track::id_type id = it->get<0>(); - trackIds.push_back(id); - } - - LMS_LOG(UI, DEBUG) << "Getting all tracks done! " << trackIds.size() << " tracks!"; -} - -} // namespace Desktop -} // namespace UserInterface - diff --git a/src/ui/audio/desktop/TrackView.hpp b/src/ui/audio/desktop/TrackView.hpp deleted file mode 100644 index 2fef0556..00000000 --- a/src/ui/audio/desktop/TrackView.hpp +++ /dev/null @@ -1,80 +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 TRACK_HPP -#define TRACK_HPP - -#include -#include - -#include "database/DatabaseHandler.hpp" - -#include "Filter.hpp" - -namespace UserInterface { -namespace Desktop { - -class TrackView : public Wt::WTableView, public Filter -{ - public: - - TrackView(Wt::WContainerWidget* parent = 0); - - // Filter interface - // Set constraints created by parent filters - void refresh(Database::SearchFilter& filter); - - // Create constraints for child filters (N/A) - void getConstraint(Database::SearchFilter& filter) {} - - // Get all the tracks that are currently selected - void getSelectedTracks(std::vector& 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& track_ids); - - typedef Wt::Signal SigTrackDoubleClicked; - typedef Wt::Signal SigStatsUpdated; - - SigTrackDoubleClicked& trackDoubleClicked() { return _sigTrackDoubleClicked; } - SigStatsUpdated& statsUpdated() { return _sigStatsUpdated; } - - private: - - SigTrackDoubleClicked _sigTrackDoubleClicked; - SigStatsUpdated _sigStatsUpdated; - - void emitStats(const Database::SearchFilter& filter); - - typedef Database::Track::UIQueryResult ResultType; - Wt::Dbo::QueryModel< ResultType > _queryModel; - Wt::WTableView* _tableView; - -}; - -} // namespace Desktop -} // namespace UserInterface - -#endif - diff --git a/src/ui/audio/mobile/ArtistSearch.cpp b/src/ui/audio/mobile/ArtistSearch.cpp deleted file mode 100644 index 0f9762a8..00000000 --- a/src/ui/audio/mobile/ArtistSearch.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2015 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 -#include -#include - -#include "LmsApplication.hpp" - -#include "ArtistSearch.hpp" - -namespace UserInterface { -namespace Mobile { - -ArtistSearch::ArtistSearch(Wt::WString title, Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent) -{ - Wt::WTemplate* t = new Wt::WTemplate(this); - t->setTemplateText(Wt::WString::tr("wa-artist-search")); - - Wt::WTemplate *titleTemplate = new Wt::WTemplate(this); - titleTemplate->setTemplateText(Wt::WString::tr("mobile-search-title")); - titleTemplate->bindString("text", title); - t->bindWidget("title", titleTemplate); - - _contents = new Wt::WContainerWidget(); - t->bindWidget("contents", _contents ); - - _showMore = new Wt::WTemplate(); - _showMore->setTemplateText(Wt::WString::tr("mobile-search-more")); - _showMore->bindString("text", "Tap to show more results..."); - _showMore->hide(); - _showMore->clicked().connect(std::bind([=] { - _sigShowMore.emit(); - })); - t->bindWidget("show-more", _showMore); -} - -void -ArtistSearch::clear() -{ - _contents->clear(); - _showMore->hide(); -} - -void -ArtistSearch::search(Database::SearchFilter filter, size_t nb) -{ - _filter = filter; - - clear(); - addResults(nb); -} - -void -ArtistSearch::addResults(std::size_t nb) -{ - using namespace Database; - - Wt::Dbo::Transaction transaction(DboSession()); - - bool moreResults; - std::vector artists = Artist::getByFilter(DboSession(), _filter, _contents->count(), nb, moreResults); - - for (Artist::pointer artist : artists) - { - Wt::WTemplate* res = new Wt::WTemplate(_contents); - res->setTemplateText(Wt::WString::tr("wa-artist-search-res")); - - Wt::WAnchor *coverAnchor = new Wt::WAnchor(Wt::WLink(Wt:: WLink::InternalPath, "/audio/artist/" + std::to_string(artist.id()))); - Wt::WImage *artistImg = new Wt::WImage(coverAnchor); - artistImg->setImageLink( SessionImageResource()->getArtistUrl(artist.id(), 512)); - artistImg->setStyleClass("center-block"); // TODO move in css? - artistImg->setStyleClass("release_res_shadow release_img-responsive"); // TODO move in css? - - res->bindWidget("gif", coverAnchor); - res->bindString("name", Wt::WString::fromUTF8(artist->getName(), Wt::PlainText)); - } - - if (moreResults) - _showMore->show(); - else - _showMore->hide(); -} - -} // namespace Mobile -} // namespace UserInterface diff --git a/src/ui/audio/mobile/ArtistSearch.hpp b/src/ui/audio/mobile/ArtistSearch.hpp deleted file mode 100644 index 50dc22de..00000000 --- a/src/ui/audio/mobile/ArtistSearch.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2015 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 UI_MOBILE_ARTIST_SEARCH_HPP -#define UI_MOBILE_ARTIST_SEARCH_HPP - -#include - -#include "database/Types.hpp" -#include "database/SearchFilter.hpp" - -namespace UserInterface { -namespace Mobile { - -class ArtistSearch : public Wt::WContainerWidget -{ - public: - - ArtistSearch(Wt::WString title, Wt::WContainerWidget *parent = 0); - - void search(Database::SearchFilter filter, std::size_t nb); - void addResults(std::size_t nb); - - // Slots - Wt::Signal& showMore() { return _sigShowMore;} - - private: - - Wt::Signal _sigShowMore; - - void clear(void); - - Wt::WTemplate* _showMore; - - Database::SearchFilter _filter; - Wt::WContainerWidget* _contents; - std::size_t _count; -}; - -} // namespace Mobile -} // namespace UserInterface - -#endif diff --git a/src/ui/audio/mobile/ArtistSearchView.cpp b/src/ui/audio/mobile/ArtistSearchView.cpp deleted file mode 100644 index d05f00dd..00000000 --- a/src/ui/audio/mobile/ArtistSearchView.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2015 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 - -#include "utils/Logger.hpp" - -#include "SearchUtils.hpp" - -#include "ArtistSearch.hpp" - -#include "ArtistSearchView.hpp" - -namespace UserInterface { -namespace Mobile { - -#define SEARCH_NB_ITEMS 20 - -using namespace Database; - -ArtistSearchView::ArtistSearchView(Wt::WContainerWidget* parent) -{ - ArtistSearch* artistSearch = new ArtistSearch("Artists", this); - artistSearch->showMore().connect(std::bind([=] { - artistSearch->addResults(SEARCH_NB_ITEMS); - })); - - wApp->internalPathChanged().connect(std::bind([=] (std::string path) - { - const std::string pathPrefix = "/audio/search/artist"; - - if (!wApp->internalPathMatches(pathPrefix)) - return; - - std::vector keywords = searchPathToSearchKeywords(path.substr(pathPrefix.length())); - - artistSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Artist, keywords), SEARCH_NB_ITEMS); - - }, std::placeholders::_1)); -} - -} // namespace Mobile -} // namespace UserInterface - diff --git a/src/ui/audio/mobile/ArtistView.cpp b/src/ui/audio/mobile/ArtistView.cpp deleted file mode 100644 index 0d365624..00000000 --- a/src/ui/audio/mobile/ArtistView.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2015 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 "LmsApplication.hpp" - -#include "utils/Utils.hpp" - -#include "ReleaseSearch.hpp" -#include "ArtistView.hpp" - -namespace UserInterface { -namespace Mobile { - -using namespace Database; - -ArtistView::ArtistView(Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent) -{ - ReleaseSearch *releases = new ReleaseSearch(this); - - wApp->internalPathChanged().connect(std::bind([=] (std::string path) - { - const std::string pathPrefix = "/audio/artist/"; - - if (!wApp->internalPathMatches(pathPrefix)) - return; - - std::string strId = path.substr(pathPrefix.length()); - - Artist::id_type id; - if (readAs(strId, id)) - { - Wt::Dbo::Transaction transaction(DboSession()); - - auto artist = Artist::getById(DboSession(), id); - releases->search(SearchFilter::ById(SearchFilter::Field::Artist, id), 20, artist ? Wt::WString::fromUTF8(artist->getName()) : "Unknown artist"); - } - - }, std::placeholders::_1)); -} - -Wt::WLink -ArtistView::getLink(Database::Artist::id_type id) -{ - return Wt::WLink(Wt::WLink::InternalPath, "/audio/artist/" + std::to_string(id)); -} - -} //namespace Mobile -} //namespace UserInterface - diff --git a/src/ui/audio/mobile/MobileAudio.cpp b/src/ui/audio/mobile/MobileAudio.cpp deleted file mode 100644 index ab08aa30..00000000 --- a/src/ui/audio/mobile/MobileAudio.cpp +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2015 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 -#include -#include - -#include "audio/AudioPlayer.hpp" - -#include "LmsApplication.hpp" - -#include "PreviewSearchView.hpp" -#include "ArtistSearchView.hpp" -#include "ReleaseSearchView.hpp" -#include "TrackSearchView.hpp" - -#include "ArtistView.hpp" -#include "ReleaseView.hpp" - -#include "MobilePlayQueue.hpp" - -#include "utils/Logger.hpp" -#include "utils/Utils.hpp" - -#include "MobileAudio.hpp" - - -namespace UserInterface { -namespace Mobile { - -enum WidgetIdx -{ - WidgetIdxSearchPreview = 0, - WidgetIdxSearchArtist = 1, - WidgetIdxSearchRelease = 2, - WidgetIdxSearchTrack = 3, - WidgetIdxArtist = 4, - WidgetIdxRelease = 5, - WidgetIdxPlayQueue = 6, -}; - -using namespace Database; - -void -Audio::search(std::string text) -{ - wApp->setInternalPath("/audio/search/preview/" + stringToUTF8(text), true); -} - -Audio::Audio(Wt::WContainerWidget *parent) -: UserInterface::Audio(parent) -{ - // Root div has to be a "container" - this->setStyleClass("container-fluid"); - this->setPadding(60, Wt::Bottom); - - Wt::WStackedWidget *stack = new Wt::WStackedWidget(this); - - PlayQueue *playQueue = new PlayQueue(); - - // Same order as WidgetIdxXXX - stack->addWidget(new PreviewSearchView(_playQueueEvents)); - stack->addWidget(new ArtistSearchView()); - stack->addWidget(new ReleaseSearchView()); - stack->addWidget(new TrackSearchView(_playQueueEvents)); - stack->addWidget(new ArtistView()); - stack->addWidget(new ReleaseView(_playQueueEvents)); - stack->addWidget(playQueue); - - wApp->internalPathChanged().connect(std::bind([=] (std::string path) - { - // order is important: most specific path first - static std::map indexes = - { - { "/audio/search/preview", WidgetIdxSearchPreview}, - { "/audio/search/artist", WidgetIdxSearchArtist}, - { "/audio/search/release", WidgetIdxSearchRelease}, - { "/audio/search/track", WidgetIdxSearchTrack}, - { "/audio/artist", WidgetIdxArtist}, - { "/audio/release", WidgetIdxRelease}, - { "/audio/playqueue", WidgetIdxPlayQueue}, - }; - - for (auto index : indexes) - { - if (wApp->internalPathMatches(index.first)) - stack->setCurrentIndex(index.second); - } - - }, std::placeholders::_1)); - - Wt::WTemplate* footer = new Wt::WTemplate(this); - footer->setTemplateText(Wt::WString::tr("mobile-audio-footer")); - - AudioPlayer* audioPlayer = new AudioPlayer(AudioPlayer::ControlPlayqueue); - footer->bindWidget("player", audioPlayer); - - audioPlayer->showPlayQueue().connect(std::bind([=] - { - wApp->setInternalPath("/audio/playqueue", true); - })); - - // Connect the audio player events to the playqueue - playQueue->playTrack().connect(std::bind([=] (Database::Track::id_type id) - { - audioPlayer->loadTrack(id); - }, std::placeholders::_1)); - audioPlayer->playNext().connect(std::bind([=] - { - playQueue->playNext(); - })); - audioPlayer->playPrevious().connect(std::bind([=] - { - playQueue->playPrevious(); - })); - audioPlayer->playbackEnded().connect(std::bind([=] - { - playQueue->playNext(); - })); - audioPlayer->shuffle().connect(std::bind(&PlayQueue::setShuffle, playQueue, std::placeholders::_1)); - audioPlayer->loop().connect(std::bind(&PlayQueue::setLoop, playQueue, std::placeholders::_1)); - - // Connect the events to the player - _playQueueEvents.trackPlay.connect(std::bind([=] (Database::Track::id_type id) - { - std::size_t pos = playQueue->addTrack(id); - playQueue->play(pos); - }, std::placeholders::_1)); - - _playQueueEvents.trackAdd.connect(std::bind([=] (Database::Track::id_type id) - { - playQueue->addTrack(id); - }, std::placeholders::_1)); - - -} - -} // namespace Mobile -} // namespace UserInterface - diff --git a/src/ui/audio/mobile/MobilePlayQueue.cpp b/src/ui/audio/mobile/MobilePlayQueue.cpp deleted file mode 100644 index c14d0ff7..00000000 --- a/src/ui/audio/mobile/MobilePlayQueue.cpp +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) 2016 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 -#include -#include - -#include "utils/Logger.hpp" - -#include "LmsApplication.hpp" - -#include "MobilePlayQueue.hpp" - - -namespace UserInterface { -namespace Mobile { - - -PlayQueue::PlayQueue(Wt::WContainerWidget* parent) -: Wt::WContainerWidget(parent) -{ - Wt::WTemplate* t = new Wt::WTemplate(this); - t->setTemplateText(Wt::WString::tr("wa-playqueue-view")); - - _trackContainer = new Wt::WContainerWidget(); - t->bindWidget("contents", _trackContainer); - - Wt::WTemplate* controls = new Wt::WTemplate(); - t->bindWidget("controls", controls); - controls->setTemplateText(Wt::WString::tr("wa-playqueue-controls")); - - Wt::WText *loadBtn = new Wt::WText("", Wt::XHTMLText); - controls->bindWidget("load", loadBtn); - loadBtn->setStyleClass("mobile-btn"); - - Wt::WText *saveBtn = new Wt::WText("", Wt::XHTMLText); - controls->bindWidget("save", saveBtn); - saveBtn->setStyleClass("mobile-btn"); - - Wt::WText *clearBtn = new Wt::WText("", Wt::XHTMLText); - controls->bindWidget("clear", clearBtn); - clearBtn->setStyleClass("mobile-btn"); - clearBtn->clicked().connect(std::bind([=] - { - clear(); - })); - - _shuffleBtn = new Wt::WText("", Wt::XHTMLText); - controls->bindWidget("shuffle", _shuffleBtn); - _shuffleBtn->setStyleClass("mobile-btn"); - _shuffleBtn->clicked().connect(std::bind([=] () - { - setShuffle(!_shuffle); - })); - - _loopBtn = new Wt::WText("", Wt::XHTMLText); - controls->bindWidget("repeat", _loopBtn); - _loopBtn->setStyleClass("mobile-btn"); - _loopBtn->clicked().connect(std::bind([=] () - { - setLoop(!_loop); - })); - -} - -std::size_t -PlayQueue::addTrack(Database::Track::id_type id) -{ - Wt::Dbo::Transaction transaction(DboSession()); - - Database::Track::pointer track = Database::Track::getById(DboSession(), id); - if (!track) - { - LMS_LOG(UI, INFO) << "No track found for id " << id; - return 0; - } - - std::size_t trackPos = _trackIds.size(); - _trackIds.push_back(id); - - Wt::WTemplate* t = new Wt::WTemplate(_trackContainer); - t->setTemplateText(Wt::WString::tr("wa-playqueue-track")); - - Wt::WImage *cover = new Wt::WImage(); - t->bindWidget("cover", cover); - cover->setStyleClass ("center-block img-responsive"); - cover->setImageLink(SessionImageResource()->getTrackUrl(track.id(), 64)); - t->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); - t->bindString("artist-name", Wt::WString::fromUTF8(track->getArtist()->getName()), Wt::PlainText); - - Wt::WText *playBtn = new Wt::WText("", Wt::XHTMLText); - t->bindWidget("play-btn", playBtn); - playBtn->addStyleClass("mobile-btn"); - playBtn->clicked().connect(std::bind([=] - { - int pos = _trackContainer->indexOf(t); - play(pos); - })); - - Wt::WText *delBtn = new Wt::WText("", Wt::XHTMLText); - t->bindWidget("del-btn", delBtn); - delBtn->addStyleClass("mobile-btn"); - delBtn->clicked().connect(std::bind([=] - { - int pos = _trackContainer->indexOf(t); - _trackContainer->removeWidget(t); - _trackIds.erase(_trackIds.begin() + pos); - })); - - return trackPos; -} - -void -PlayQueue::clear(void) -{ - _trackIds.clear(); - _trackContainer->clear(); - _currentPos = 0; -} - -void -PlayQueue::play(size_t pos) -{ - if (_trackIds.empty() || pos >= _trackIds.size()) - return; - - _trackContainer->widget(_currentPos)->removeStyleClass("playqueue-playing"); - - _currentPos = pos; - _trackContainer->widget(_currentPos)->addStyleClass("playqueue-playing"); - - _sigTrackPlay.emit(_trackIds[pos]); -} - -void -PlayQueue::playNext(void) -{ - if (_currentPos == (_trackIds.size() - 1) && _loop) - play(0); - else - play(_currentPos + 1); -} - -void -PlayQueue::playPrevious(void) -{ - if (_currentPos == 0) - { - if (_loop) - { - play(_trackIds.size() - 1); - } - else - return; - } - else - play(_currentPos - 1); -} - -void -PlayQueue::handlePlaybackComplete(void) -{ - playNext(); -} - -void -PlayQueue::setShuffle(bool value) -{ - _shuffle = value; - - if (value) - _shuffleBtn->addStyleClass("mobile-btn-active"); - else - _shuffleBtn->removeStyleClass("mobile-btn-active"); -} - -void -PlayQueue::setLoop(bool value) -{ - _loop = value; - - if (value) - _loopBtn->addStyleClass("mobile-btn-active"); - else - _loopBtn->removeStyleClass("mobile-btn-active"); -} - -} // namespace Mobile -} // namespace UserInterface diff --git a/src/ui/audio/mobile/MobilePlayQueue.hpp b/src/ui/audio/mobile/MobilePlayQueue.hpp deleted file mode 100644 index ab8a1dfc..00000000 --- a/src/ui/audio/mobile/MobilePlayQueue.hpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2016 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 . - */ - -#pragma once - -#include - -#include -#include - -#include "database/Types.hpp" - -namespace UserInterface { -namespace Mobile { - -class PlayQueue : public Wt::WContainerWidget -{ - public: - PlayQueue(Wt::WContainerWidget* parent = 0); - - // Contents - std::size_t addArtist(Database::Artist::id_type id); - std::size_t addRelease(Database::Release::id_type id); - std::size_t addTrack(Database::Track::id_type id); - void clear(void); - - // Controls - void play(size_t pos); // Play the track at a given position - void playNext(void); - void playPrevious(void); - - void setShuffle(bool enable); // true to enable shuffle - void setLoop(bool enable); // true to enable loop - - // Signals - // Emitted when a song has to be played - Wt::Signal& playTrack() { return _sigTrackPlay; } - - // Slots - // Song has finished - void handlePlaybackComplete(void); - - private: - - bool _loop = false; - bool _shuffle = false; - std::size_t _currentPos = 0; - - Wt::WText* _shuffleBtn = nullptr; - Wt::WText* _loopBtn = nullptr; - - Wt::Signal _sigTrackPlay; - - std::vector _trackIds; - Wt::WContainerWidget* _trackContainer; -}; - -} // namespace Mobile -} // namespace UserInterface diff --git a/src/ui/audio/mobile/PreviewSearchView.cpp b/src/ui/audio/mobile/PreviewSearchView.cpp deleted file mode 100644 index 8a602e24..00000000 --- a/src/ui/audio/mobile/PreviewSearchView.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2015 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 - -#include "utils/Logger.hpp" - -#include "SearchUtils.hpp" - -#include "ArtistSearch.hpp" -#include "ReleaseSearch.hpp" -#include "TrackSearch.hpp" - -#include "PreviewSearchView.hpp" - -namespace UserInterface { -namespace Mobile { - -#define SEARCH_NB_ITEMS 4 - -using namespace Database; - -PreviewSearchView::PreviewSearchView(PlayQueueEvents& events, Wt::WContainerWidget* parent) -{ - const std::string pathPrefix = "/audio/search/preview"; - - ArtistSearch* artistSearch = new ArtistSearch("Artists", this); - ReleaseSearch* releaseSearch = new ReleaseSearch(this); - TrackSearch* trackSearch = new TrackSearch(events, this); - - artistSearch->showMore().connect(std::bind([=] - { - std::string path = wApp->internalPath(); - path.replace(0, pathPrefix.length(), "/audio/search/artist"); - - wApp->setInternalPath(path, true); - })); - - releaseSearch->showMore().connect(std::bind([=] - { - std::string path = wApp->internalPath(); - path.replace(0, pathPrefix.length(), "/audio/search/release"); - - wApp->setInternalPath(path, true); - })); - - trackSearch->showMore().connect(std::bind([=] - { - std::string path = wApp->internalPath(); - path.replace(0, pathPrefix.length(), "/audio/search/track"); - - wApp->setInternalPath(path, true); - })); - - wApp->internalPathChanged().connect(std::bind([=] (std::string path) - { - if (!wApp->internalPathMatches(pathPrefix)) - return; - - std::vector keywords = searchPathToSearchKeywords(path.substr(pathPrefix.length())); - - artistSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Artist, keywords), SEARCH_NB_ITEMS); - releaseSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Release, keywords), SEARCH_NB_ITEMS, "Releases"); - trackSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Track, keywords), SEARCH_NB_ITEMS); - - }, std::placeholders::_1)); -} - -} // namespace Mobile -} // namespace UserInterface - diff --git a/src/ui/audio/mobile/ReleaseSearch.cpp b/src/ui/audio/mobile/ReleaseSearch.cpp deleted file mode 100644 index c45c83cc..00000000 --- a/src/ui/audio/mobile/ReleaseSearch.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2015 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 -#include -#include - -#include "utils/Logger.hpp" - -#include "LmsApplication.hpp" - -#include "ReleaseSearch.hpp" -#include "ReleaseView.hpp" -#include "ArtistView.hpp" - -namespace UserInterface { -namespace Mobile { - -using namespace Database; - -ReleaseSearch::ReleaseSearch(Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent) -{ - Wt::WTemplate* t = new Wt::WTemplate(this); - t->setTemplateText(Wt::WString::tr("wa-release-search")); - - Wt::WTemplate *titleTemplate = new Wt::WTemplate(this); - titleTemplate->setTemplateText(Wt::WString::tr("mobile-search-title")); - - _title = new Wt::WText(); - titleTemplate->bindWidget("text", _title); - - t->bindWidget("title", titleTemplate); - - _contents = new Wt::WContainerWidget(); - t->bindWidget("contents", _contents ); - - _showMore = new Wt::WTemplate(); - _showMore->setTemplateText(Wt::WString::tr("mobile-search-more")); - _showMore->bindString("text", "Tap to show more results..."); - _showMore->hide(); - _showMore->clicked().connect(std::bind([=] { - _sigShowMore.emit(); - })); - - t->bindWidget("show-more", _showMore); -} - -void -ReleaseSearch::clear() -{ - _contents->clear(); - _showMore->hide(); -} - -void -ReleaseSearch::search(Database::SearchFilter filter, size_t max, Wt::WString title) -{ - _filter = filter; - _title->setText(title); - - clear(); - addResults(max); -} - -void -ReleaseSearch::addResults(size_t nb) -{ - using namespace Database; - - Wt::Dbo::Transaction transaction(DboSession()); - - bool moreResults; - std::vector releases = Release::getByFilter(DboSession(), _filter, _contents->count(), nb, moreResults); - - for (Release::pointer release : releases) - { - Wt::WTemplate* res = new Wt::WTemplate(_contents); - res->setTemplateText(Wt::WString::tr("wa-release-search-res")); - - Wt::WAnchor *coverAnchor = new Wt::WAnchor(ReleaseView::getLink(release.id())); - Wt::WImage *cover = new Wt::WImage(coverAnchor); - cover->setStyleClass("center-block"); - cover->setImageLink( SessionImageResource()->getReleaseUrl(release.id(), 512)); - cover->setStyleClass("release_res_shadow release_img-responsive"); // TODO move? - - res->bindWidget("cover", coverAnchor); - res->bindWidget("name", new Wt::WText(Wt::WString::fromUTF8(release->getName()), Wt::PlainText)); - res->bindString("release_name", Wt::WString::fromUTF8(release->getName()), Wt::PlainText); - - bool variousArtists; - auto artists = Artist::getByFilter(DboSession(), - SearchFilter::ById(SearchFilter::Field::Release, release.id()), 0, 1, variousArtists); - if (!artists.empty()) - { - if (variousArtists) - { - res->bindWidget("artist", new Wt::WText(Wt::WString::fromUTF8("Various Artists", Wt::PlainText))); - } - else - { - Wt::WAnchor *artistAnchor = new Wt::WAnchor(ArtistView::getLink(artists.front().id())); - Wt::WText *artist = new Wt::WText(artistAnchor); - artist->setText(Wt::WString::fromUTF8(artists.front()->getName(), Wt::PlainText)); - res->bindWidget("artist", artistAnchor); - } - } - } - - if (moreResults) - _showMore->show(); - else - _showMore->hide(); - -} - -} // namespace Mobile -} // namespace UserInterface diff --git a/src/ui/audio/mobile/ReleaseSearch.hpp b/src/ui/audio/mobile/ReleaseSearch.hpp deleted file mode 100644 index 3e82eb57..00000000 --- a/src/ui/audio/mobile/ReleaseSearch.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#pragma once - -#include -#include - -#include "database/SearchFilter.hpp" -#include "database/Types.hpp" - -namespace UserInterface { -namespace Mobile { - -class ReleaseSearch : public Wt::WContainerWidget -{ - public: - - ReleaseSearch(Wt::WContainerWidget *parent = 0); - - void search(Database::SearchFilter filter, std::size_t nb, Wt::WString title); - void addResults(std::size_t nb); - - // Slots - Wt::Signal& showMore() { return _sigShowMore;} - - private: - - Wt::Signal _sigShowMore; - - void clear(void); - - Wt::WTemplate* _showMore; - Database::SearchFilter _filter; - Wt::WContainerWidget* _contents; - Wt::WText* _title; - std::size_t _count; -}; - -} // namespace Mobile -} // namespace UserInterface - diff --git a/src/ui/audio/mobile/ReleaseSearchView.cpp b/src/ui/audio/mobile/ReleaseSearchView.cpp deleted file mode 100644 index 09bfaf9f..00000000 --- a/src/ui/audio/mobile/ReleaseSearchView.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2015 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 "SearchUtils.hpp" - -#include "ReleaseSearch.hpp" - -#include "ReleaseSearchView.hpp" - -namespace UserInterface { -namespace Mobile { - -#define SEARCH_NB_ITEMS 20 - -using namespace Database; - -ReleaseSearchView::ReleaseSearchView(Wt::WContainerWidget* parent) -{ - ReleaseSearch* artistSearch = new ReleaseSearch(this); - artistSearch->showMore().connect(std::bind([=] { - artistSearch->addResults(SEARCH_NB_ITEMS); - })); - - wApp->internalPathChanged().connect(std::bind([=] (std::string path) - { - const std::string pathPrefix = "/audio/search/release"; - - if (!wApp->internalPathMatches(pathPrefix)) - return; - - std::vector keywords = searchPathToSearchKeywords(path.substr(pathPrefix.length())); - - artistSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Release, keywords), SEARCH_NB_ITEMS, "Releases"); - - }, std::placeholders::_1)); -} - -} // namespace Mobile -} // namespace UserInterface - diff --git a/src/ui/audio/mobile/ReleaseView.cpp b/src/ui/audio/mobile/ReleaseView.cpp deleted file mode 100644 index 93c1e5fc..00000000 --- a/src/ui/audio/mobile/ReleaseView.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2015 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 -#include -#include - -#include "utils/Logger.hpp" -#include "utils/Utils.hpp" - -#include "LmsApplication.hpp" - -#include "ReleaseView.hpp" - -namespace UserInterface { -namespace Mobile { - -using namespace Database; - -ReleaseView::ReleaseView(PlayQueueEvents& events, Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent), - _events(events) -{ - Wt::WTemplate* wrapper = new Wt::WTemplate(this); - wrapper->setTemplateText(Wt::WString::tr("wa-trackview-wrapper")); - - Wt::WTemplate* title = new Wt::WTemplate(this); - wrapper->bindWidget("title", title); - - title->setTemplateText(Wt::WString::tr("mobile-search-title")); - title->bindString("text", "Releases", Wt::PlainText); - - _releaseContainer = new Wt::WContainerWidget(); - wrapper->bindWidget("release-container", _releaseContainer); - - _showMore = new Wt::WTemplate(); - wrapper->bindWidget("show-more", _showMore); - - _showMore->setTemplateText(Wt::WString::tr("mobile-search-more")); - _showMore->bindString("text", "Tap to show more results..."); - _showMore->hide(); - _showMore->clicked().connect(std::bind([=] { - addResults(20); - })); - - wApp->internalPathChanged().connect(std::bind([=] (std::string path) - { - const std::string pathPrefix = "/audio/release/"; - - if (!wApp->internalPathMatches(pathPrefix)) - return; - - std::string strId = path.substr(pathPrefix.length()); - - Release::id_type id; - if (readAs(strId, id)) - { - clear(); - search(SearchFilter::ById(SearchFilter::Field::Release, id), 20); - } - }, std::placeholders::_1)); -} - -void -ReleaseView::clear() -{ - // Flush the release container - _releaseContainer->clear(); - - // Flush the current context - _currentTrackContainer = nullptr; - _nbTracks = 0; - _showMore->hide(); -} - -void -ReleaseView::search(SearchFilter filter, size_t nb) -{ - _filter = filter; - - clear(); - addResults(nb); -} - -static Wt::WString -getArtistNameFromRelease(Release::pointer release) -{ - auto artists = Artist::getByFilter(DboSession(), - SearchFilter::ById(SearchFilter::Field::Release, release.id()), -1, 2); - - if (artists.size() > 1) - return Wt::WString::fromUTF8("Various artists", Wt::PlainText); - else if (artists.size() == 1) - return Wt::WString::fromUTF8(artists.front()->getName(), Wt::PlainText); - else - return Wt::WString(); -} - -void -ReleaseView::addResults(size_t nb) -{ - Wt::Dbo::Transaction transaction(DboSession()); - - bool moreResults; - std::vector tracks = Track::getByFilter(DboSession(), _filter, _nbTracks, nb, moreResults); - - for (Track::pointer track : tracks) - { - // First check if we need to create a new track container - - // New container if it is the first one or if the release has changed - if (!_currentTrackContainer - || _currentReleaseId != track->getRelease().id()) - { - Release::pointer release = track->getRelease(); - - Wt::WTemplate *releaseContainer = new Wt::WTemplate(_releaseContainer); - releaseContainer->setTemplateText(Wt::WString::tr("wa-trackview-release-container")); - _currentReleaseId = release.id(); - - Wt::WImage *cover = new Wt::WImage(); - cover->setStyleClass ("center-block img-responsive"); // TODO move to CSS? - cover->setImageLink(Wt::WLink (SessionImageResource()->getReleaseUrl(release.id(), 512))); - - releaseContainer->bindWidget("cover", cover); - releaseContainer->bindString("artist-name", getArtistNameFromRelease(release), Wt::PlainText); - releaseContainer->bindString("release-name", release->getName(), Wt::PlainText); - int year = release->getReleaseYear(); - if (year > 0) - { - releaseContainer->setCondition("if-has-year", true); - releaseContainer->bindInt("year", year); - - int originalYear = release->getReleaseYear(true); - if (originalYear > 0 && originalYear != year) - { - releaseContainer->setCondition("if-has-orig-year", true); - releaseContainer->bindInt("orig-year", originalYear); - } - } - - _currentTrackContainer = new Wt::WContainerWidget(); - releaseContainer->bindWidget("track-container", _currentTrackContainer); - } - - assert(_currentTrackContainer != nullptr); - - Wt::WTemplate* trackRes = new Wt::WTemplate(_currentTrackContainer); - trackRes->setTemplateText(Wt::WString::tr("wa-trackview-track")); - - if (track->getTrackNumber() > 0 && !track->getRelease()->isNone()) - { - trackRes->setCondition("if-has-track-num", true); - trackRes->bindInt("track-num", track->getTrackNumber()); - - if (track->getDiscNumber() > 0 && track->getTotalDiscNumber() > 1) - { trackRes->setCondition("if-has-disc-num", true); - trackRes->bindInt("disc-num", track->getDiscNumber()); - } - } - trackRes->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); - // TODO, display artist name for compilation releases? - - Wt::WText *playBtn = new Wt::WText("", Wt::XHTMLText); - trackRes->bindWidget("play-btn", playBtn); - playBtn->setStyleClass("mobile-btn"); - playBtn->clicked().connect(std::bind([=] { - _events.trackPlay.emit(track.id()); - })); - - Wt::WText *addBtn = new Wt::WText("", Wt::XHTMLText); - trackRes->bindWidget("add-btn", addBtn); - addBtn->setStyleClass("mobile-btn"); - addBtn->clicked().connect(std::bind([=] { - _events.trackAdd.emit(track.id()); - })); - - - - - _nbTracks++; - } - - if (moreResults) - _showMore->show(); - else - _showMore->hide(); -} - -Wt::WLink -ReleaseView::getLink(Database::Release::id_type id) -{ - return Wt::WLink(Wt::WLink::InternalPath, "/audio/release/" + std::to_string(id)); -} - -} // namespace Mobile -} // namespace UserInterface diff --git a/src/ui/audio/mobile/ReleaseView.hpp b/src/ui/audio/mobile/ReleaseView.hpp deleted file mode 100644 index bb590702..00000000 --- a/src/ui/audio/mobile/ReleaseView.hpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#pragma once - -#include -#include - -#include "MobilePlayQueueEvents.hpp" - -namespace UserInterface { -namespace Mobile { - -class ReleaseView : public Wt::WContainerWidget -{ - public: - - static Wt::WLink getLink(Database::Release::id_type id); - - ReleaseView(PlayQueueEvents& events, Wt::WContainerWidget *parent = 0); - - void search(Database::SearchFilter filter, size_t nb); - - // Slots - Wt::Signal& moreTracksSelected() { return _sigMoreTracksSelected;} - - private: - - Wt::Signal _sigMoreTracksSelected; - - void clear(void); - void addResults(size_t nb); - - PlayQueueEvents& _events; - Wt::WTemplate* _showMore; - - Database::SearchFilter _filter; - std::size_t _nbTracks; - - // Main container that holds the releases - Wt::WContainerWidget* _releaseContainer; - - // Used to add more results on the fly - Database::Release::id_type _currentReleaseId; - Wt::WContainerWidget* _currentTrackContainer; -}; - -} // namespace Mobile -} // namespace UserInterface - diff --git a/src/ui/audio/mobile/SearchUtils.hpp b/src/ui/audio/mobile/SearchUtils.hpp deleted file mode 100644 index 04743b15..00000000 --- a/src/ui/audio/mobile/SearchUtils.hpp +++ /dev/null @@ -1,32 +0,0 @@ - -/* - * Copyright (C) 2015 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 . - */ - -#pragma once - -#include "utils/Utils.hpp" - -static inline std::vector searchPathToSearchKeywords(std::string path) -{ - if (!path.empty() && path[0] == '/') - path.erase(path.begin()); - - return splitString(path, " \t"); -} - diff --git a/src/ui/audio/mobile/TrackSearch.cpp b/src/ui/audio/mobile/TrackSearch.cpp deleted file mode 100644 index db5d4427..00000000 --- a/src/ui/audio/mobile/TrackSearch.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2015 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 -#include -#include - -#include "utils/Logger.hpp" -#include "utils/Utils.hpp" - -#include "LmsApplication.hpp" - -#include "TrackSearch.hpp" - -namespace UserInterface { -namespace Mobile { - -using namespace Database; - -TrackSearch::TrackSearch(PlayQueueEvents& events, Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent), - _events(events) -{ - Wt::WTemplate* t = new Wt::WTemplate(this); - t->setTemplateText(Wt::WString::tr("wa-track-search")); - - Wt::WTemplate* title = new Wt::WTemplate(this); - title->setTemplateText(Wt::WString::tr("mobile-search-title")); - title->bindString("text", "Tracks", Wt::PlainText); - t->bindWidget("title", title); - - _contents = new Wt::WContainerWidget(); - t->bindWidget("contents", _contents); - - _showMore = new Wt::WTemplate(); - _showMore->setTemplateText(Wt::WString::tr("mobile-search-more")); - _showMore->bindString("text", "Tap to show more results..."); - _showMore->hide(); - _showMore->clicked().connect(std::bind([=] { - _sigShowMore.emit(); - addResults(20); - })); - t->bindWidget("show-more", _showMore); -} - -void -TrackSearch::clear() -{ - _contents->clear(); - _showMore->hide(); -} - -void -TrackSearch::search(SearchFilter filter, size_t nb) -{ - _filter = filter; - - clear(); - addResults(nb); -} - -void -TrackSearch::addResults(size_t nb) -{ - Wt::Dbo::Transaction transaction(DboSession()); - - bool moreResults; - std::vector tracks = Track::getByFilter(DboSession(), _filter, _contents->count(), nb, moreResults); - - for (Track::pointer track : tracks) - { - Wt::WTemplate* res = new Wt::WTemplate(_contents); - res->setTemplateText(Wt::WString::tr("wa-track-search-res")); - - Wt::WImage *cover = new Wt::WImage(); - res->bindWidget("cover", cover); - cover->setStyleClass ("center-block img-responsive"); - cover->setImageLink(SessionImageResource()->getTrackUrl(track.id(), 64)); - res->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); - res->bindString("artist-name", Wt::WString::fromUTF8(track->getArtist()->getName()), Wt::PlainText); - - Wt::WText *playBtn = new Wt::WText("", Wt::XHTMLText); - res->bindWidget("play-btn", playBtn); - playBtn->setStyleClass("mobile-btn"); - playBtn->clicked().connect(std::bind([=] { - _events.trackPlay.emit(track.id()); - })); - - Wt::WText *addBtn = new Wt::WText("", Wt::XHTMLText); - res->bindWidget("add-btn", addBtn); - addBtn->setStyleClass("mobile-btn"); - addBtn->clicked().connect(std::bind([=] { - _events.trackAdd.emit(track.id()); - })); - - - } - - if (moreResults) - _showMore->show(); - else - _showMore->hide(); -} - -} // namespace Mobile -} // namespace UserInterface diff --git a/src/ui/audio/mobile/TrackSearch.hpp b/src/ui/audio/mobile/TrackSearch.hpp deleted file mode 100644 index 0a98d886..00000000 --- a/src/ui/audio/mobile/TrackSearch.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#pragma once - -#include -#include - -#include "database/SearchFilter.hpp" -#include "database/Types.hpp" - -#include "MobilePlayQueueEvents.hpp" - -namespace UserInterface { -namespace Mobile { - -class TrackSearch : public Wt::WContainerWidget -{ - public: - - TrackSearch(PlayQueueEvents& events, Wt::WContainerWidget *parent = 0); - - void addResults(size_t nb); - void search(Database::SearchFilter filter, size_t nb); - - // Slots - Wt::Signal& showMore() { return _sigShowMore;} - - private: - - Wt::Signal _sigShowMore; - - void clear(void); - - PlayQueueEvents& _events; - Wt::WTemplate* _showMore; - Database::SearchFilter _filter; - Wt::WContainerWidget* _contents; -}; - -} // namespace Mobile -} // namespace UserInterface - diff --git a/src/ui/audio/mobile/TrackSearchView.cpp b/src/ui/audio/mobile/TrackSearchView.cpp deleted file mode 100644 index bff89ee0..00000000 --- a/src/ui/audio/mobile/TrackSearchView.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2016 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 - -#include "utils/Logger.hpp" - -#include "SearchUtils.hpp" - -#include "TrackSearch.hpp" - -#include "TrackSearchView.hpp" - -namespace UserInterface { -namespace Mobile { - -#define SEARCH_NB_ITEMS 20 - -using namespace Database; - -TrackSearchView::TrackSearchView(PlayQueueEvents& events, Wt::WContainerWidget* parent) -{ - TrackSearch* trackSearch = new TrackSearch(events, this); - trackSearch->showMore().connect(std::bind([=] { - trackSearch->addResults(SEARCH_NB_ITEMS); - })); - - wApp->internalPathChanged().connect(std::bind([=] (std::string path) - { - const std::string pathPrefix = "/audio/search/track"; - - if (!wApp->internalPathMatches(pathPrefix)) - return; - - std::vector keywords = searchPathToSearchKeywords(path.substr(pathPrefix.length())); - - trackSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Track, keywords), SEARCH_NB_ITEMS); - - }, std::placeholders::_1)); -} - -} // namespace Mobile -} // namespace UserInterface - diff --git a/src/ui/settings/Settings.cpp b/src/ui/settings/Settings.cpp deleted file mode 100644 index 52e75376..00000000 --- a/src/ui/settings/Settings.cpp +++ /dev/null @@ -1,110 +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 -#include -#include - -#include "SettingsAudioFormView.hpp" -#include "SettingsUserFormView.hpp" -#include "SettingsAccountFormView.hpp" -#include "SettingsDatabaseFormView.hpp" -#include "SettingsMediaDirectories.hpp" -#include "SettingsUsers.hpp" - -#include "utils/Logger.hpp" - -#include "database/Setting.hpp" -#include "database/updater/DatabaseUpdater.hpp" - -#include "LmsApplication.hpp" - -#include "Settings.hpp" - -namespace UserInterface { -namespace Settings { - -using namespace Database; - -Settings::Settings(Wt::WContainerWidget* parent) -: Wt::WContainerWidget(parent) -{ - Wt::WHBoxLayout* hLayout = new Wt::WHBoxLayout(this); - - // Create a stack where the contents will be located. - Wt::WStackedWidget *contents = new Wt::WStackedWidget(); - - contents->setStyleClass("contents"); - contents->setOverflow(WContainerWidget::OverflowHidden); - - // TODO menu style on hover - Wt::WMenu *menu = new Wt::WMenu(contents, Wt::Vertical); - menu->setStyleClass("nav nav-pills nav-stacked submenu"); - menu->setWidth(150); - menu->setInternalPathEnabled(); - menu->setInternalBasePath("/settings/"); - - hLayout->addWidget(menu); - hLayout->addWidget(contents, 1); - - std::string userId; - bool userIsAdmin; - { - Wt::Dbo::Transaction transaction(DboSession()); - - userId = User::getId(CurrentUser()); - userIsAdmin = CurrentUser()->isAdmin(); - } - - menu->addItem("Audio", new AudioFormView())->setPathComponent("audio"); - if (userIsAdmin) - { - MediaDirectories* mediaDirectories = new MediaDirectories(); - mediaDirectories->changed().connect(std::bind([=] - { - LMS_LOG(UI, INFO) << "Media directories have changed: requesting imediate scan"; - - // On directory change, request an immediate scan - std::lock_guard lock(Updater::instance().getMutex()); - - Setting::setBool(DboSession(), "manual_scan_requested", true); - Updater::instance().restart(); - })); - menu->addItem("Media Folders", mediaDirectories)->setPathComponent("mediadirectories"); - - DatabaseFormView* databaseFormView = new DatabaseFormView(); - databaseFormView->changed().connect(std::bind([=] - { - std::lock_guard lock(Updater::instance().getMutex()); - Updater::instance().restart(); - })); - menu->addItem("Database", databaseFormView)->setPathComponent("database"); - - menu->addItem("Users", new Users())->setPathComponent("users"); - } - else - { - menu->addItem("Account", new AccountFormView(userId))->setPathComponent("account"); - } - -} - -} // namespace Settings -} // namespace UserInterface diff --git a/src/ui/settings/SettingsAccountFormView.cpp b/src/ui/settings/SettingsAccountFormView.cpp deleted file mode 100644 index bbffbd2a..00000000 --- a/src/ui/settings/SettingsAccountFormView.cpp +++ /dev/null @@ -1,285 +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 -#include -#include -#include -#include -#include - -#include "utils/Logger.hpp" - -#include "common/Validators.hpp" -#include "LmsApplication.hpp" - -#include "SettingsAccountFormView.hpp" - -namespace UserInterface { -namespace Settings { - -class AccountFormModel : public Wt::WFormModel -{ - public: - - // Associate each field with a unique string literal. - static const Field NameField; - static const Field EmailField; - static const Field PasswordField; - static const Field PasswordConfirmField; - - AccountFormModel(std::string userId, Wt::WObject *parent = 0) - : Wt::WFormModel(parent), - _userId(userId) - { - addField(NameField); - addField(EmailField); - addField(PasswordField); - addField(PasswordConfirmField); - - setValidator(NameField, createNameValidator()); - setValidator(EmailField, createEmailValidator()); - - // populate the model with initial data - loadData(); - } - - void loadData() - { - Wt::Dbo::Transaction transaction(DboSession()); - - Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId( _userId ); - Database::User::pointer user = DbHandler().getUser(authUser); - - if (user && authUser.isValid()) - { - setValue(NameField, authUser.identity(Wt::Auth::Identity::LoginName)); - if (!authUser.email().empty()) - setValue(EmailField, authUser.email()); - else - setValue(EmailField, authUser.unverifiedEmail()); - } - setValue(PasswordField, Wt::WString()); - setValue(PasswordConfirmField, Wt::WString()); - } - - bool saveData(Wt::WString& error) - { - // DBO transaction active here - try { - Wt::Dbo::Transaction transaction(DboSession()); - - // Update user - Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId(_userId); - Database::User::pointer user = DbHandler().getUser( authUser ); - - // user may have been deleted by someone else - if (!authUser.isValid()) { - error = Wt::WString("User identity does not exist"); - return false; - } - else if(!user) - { - error = Wt::WString("User not found"); - return false; - } - - // Account - authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(NameField)); - authUser.setEmail(valueText(EmailField).toUTF8()); - - // Password - if (!valueText(PasswordField).empty()) - Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); - - setValue(PasswordField, Wt::WString()); - setValue(PasswordConfirmField, Wt::WString()); - - } - catch(Wt::Dbo::Exception& exception) - { - LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what(); - return false; - } - - return true; - } - - bool validateField(Field field) - { - // DBO transaction active here - - Wt::WString error; - - if (field == NameField) - { - Wt::Dbo::Transaction transaction(DboSession()); - // Must be unique since used as LoginIdentity - Wt::Auth::User user = DbHandler().getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, valueText(field)); - if (user.isValid() && user.id() != _userId) - error = "Already exists"; - else - return Wt::WFormModel::validateField(field); - } - else if (field == PasswordField) - { - // Password is mandatory if we create the user - if (!valueText(PasswordField).empty()) - { - // Evaluate the strength of the password - Wt::Auth::AbstractPasswordService::StrengthValidatorResult res - = Database::Handler::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), - valueText(NameField), - valueText(EmailField).toUTF8()); - - if (!res.isValid()) - error = res.message(); - } - else - return Wt::WFormModel::validateField(field); - } - else if (field == PasswordConfirmField) - { - if (validation(PasswordField).state() == Wt::WValidator::Valid) - { - if (valueText(PasswordField) != valueText(PasswordConfirmField)) - error = Wt::WString::tr("Wt.Auth.passwords-dont-match"); - } - } - else - { - // Apply validators - return Wt::WFormModel::validateField(field); - } - - setValidation(field, Wt::WValidator::Result( error.empty() ? Wt::WValidator::Valid : Wt::WValidator::Invalid, error)); - - return validation(field).state() == Wt::WValidator::Valid; - } - - - private: - - std::string _userId; -}; - -const Wt::WFormModel::Field AccountFormModel::NameField = "name"; -const Wt::WFormModel::Field AccountFormModel::EmailField = "email"; -const Wt::WFormModel::Field AccountFormModel::PasswordField = "password"; -const Wt::WFormModel::Field AccountFormModel::PasswordConfirmField = "password-confirm"; - -AccountFormView::AccountFormView(std::string userId, Wt::WContainerWidget *parent) -: Wt::WTemplateFormView(parent) -{ - - _model = new AccountFormModel(userId, this); - - setTemplateText(tr("settings-account")); - addFunction("id", &WTemplate::Functions::id); - addFunction("block", &WTemplate::Functions::id); - - _applyInfo = new Wt::WText(); - _applyInfo->setInline(false); - _applyInfo->hide(); - bindWidget("apply-info", _applyInfo); - - // Name - Wt::WLineEdit* accountEdit = new Wt::WLineEdit(); - setFormWidget(AccountFormModel::NameField, accountEdit); - accountEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Email - Wt::WLineEdit* emailEdit = new Wt::WLineEdit(); - setFormWidget(AccountFormModel::EmailField, emailEdit); - emailEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Password - Wt::WLineEdit* passwordEdit = new Wt::WLineEdit(); - setFormWidget(AccountFormModel::PasswordField, passwordEdit ); - passwordEdit->setEchoMode(Wt::WLineEdit::Password); - passwordEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Password confirmation - Wt::WLineEdit* passwordConfirmEdit = new Wt::WLineEdit(); - setFormWidget(AccountFormModel::PasswordConfirmField, passwordConfirmEdit); - passwordConfirmEdit->setEchoMode(Wt::WLineEdit::Password); - passwordConfirmEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Title & Buttons - bindString("title", "Account settings"); - - Wt::WPushButton *saveButton = new Wt::WPushButton("Apply"); - saveButton->setStyleClass("btn-primary"); - bindWidget("save-button", saveButton); - saveButton->clicked().connect(this, &AccountFormView::processSave); - - Wt::WPushButton *cancelButton = new Wt::WPushButton("Discard"); - bindWidget("cancel-button", cancelButton); - cancelButton->clicked().connect(this, &AccountFormView::processCancel); - - updateView(_model); - -} - - void -AccountFormView::processCancel() -{ - _applyInfo->show(); - - _applyInfo->setText( Wt::WString::fromUTF8("Parameters reverted!")); - _applyInfo->setStyleClass("alert alert-info"); - _model->loadData(); - - _model->validate(); - updateView(_model); -} - -void -AccountFormView::processSave() -{ - updateModel(_model); - - _applyInfo->show(); - if (_model->validate()) - { - Wt::WString error; - // commit model into DB - if (_model->saveData(error) ) - { - _applyInfo->setText( Wt::WString::fromUTF8("New parameters successfully applied!")); - _applyInfo->setStyleClass("alert alert-success"); - } - else - { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters: ") + error); - _applyInfo->setStyleClass("alert alert-danger"); - } - } - else - { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters!")); - _applyInfo->setStyleClass("alert alert-danger"); - } - updateView(_model); -} - -} // namespace Settings -} // namespace UserInterface diff --git a/src/ui/settings/SettingsAccountFormView.hpp b/src/ui/settings/SettingsAccountFormView.hpp deleted file mode 100644 index ebe02e5e..00000000 --- a/src/ui/settings/SettingsAccountFormView.hpp +++ /dev/null @@ -1,49 +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 UI_SETTINGS_ACCOUNT_FORM_VIEW_HPP -#define UI_SETTINGS_ACCOUNT_FORM_VIEW_HPP - -#include -#include -#include - -namespace UserInterface { -namespace Settings { - -class AccountFormModel; - -class AccountFormView : public Wt::WTemplateFormView -{ - public: - AccountFormView(std::string userId, Wt::WContainerWidget *parent = 0); - - private: - void processCancel(); // reload from DB - void processSave(); // commit into DB - - AccountFormModel* _model; - Wt::WText* _applyInfo; - -}; - -} // namespace Settings -} // namespace UserInterface - -#endif diff --git a/src/ui/settings/SettingsAudioFormView.cpp b/src/ui/settings/SettingsAudioFormView.cpp deleted file mode 100644 index 91c49015..00000000 --- a/src/ui/settings/SettingsAudioFormView.cpp +++ /dev/null @@ -1,258 +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 -#include -#include -#include - -#include "utils/Logger.hpp" - -#include "common/Validators.hpp" -#include "LmsApplication.hpp" - -#include "SettingsAudioFormView.hpp" - -namespace { -std::string encodingToString(Database::AudioEncoding encoding) -{ - - switch (encoding) - { - case Database::AudioEncoding::AUTO: return "Automatic"; - case Database::AudioEncoding::MP3: return "MP3"; - case Database::AudioEncoding::OGA: return "OGG"; - case Database::AudioEncoding::WEBMA: return "WebM"; - } - - return "?"; -} -} - -namespace UserInterface { -namespace Settings { - -class AudioFormModel : public Wt::WFormModel -{ - public: - - // Associate each field with a unique string literal. - static const Field BitrateField; - static const Field EncodingField; - - AudioFormModel(Wt::WObject *parent = 0) - : Wt::WFormModel(parent) - { - initializeModels(); - - addField(BitrateField); - addField(EncodingField); - - setValidator(BitrateField, new Wt::WValidator(true)); // mandatory - setValidator(EncodingField, new Wt::WValidator(true)); // mandatory - - // populate the model with initial data - loadData(); - } - - Wt::WAbstractItemModel *bitrateModel() { return _bitrateModel; } - Wt::WAbstractItemModel *encodingModel() { return _encodingModel; } - - void loadData() - { - Wt::Dbo::Transaction transaction(DboSession()); - - setValue(BitrateField, std::min(CurrentUser()->getMaxAudioBitrate(), CurrentUser()->getAudioBitrate()) / 1000); // in kps - int encodingRow = getAudioEncodingRow( CurrentUser()->getAudioEncoding()); - setValue(EncodingField, getAudioEncodingString(encodingRow)); - } - - bool saveData(Wt::WString& error) - { - // DBO transaction active here - try { - Wt::Dbo::Transaction transaction(DboSession()); - - // user may have been deleted by someone else - CurrentUser().modify()->setAudioBitrate( Wt::asNumber(value(BitrateField)) * 1000); // in kbps - - int encodingRow = getAudioEncodingRow( boost::any_cast(value((EncodingField)))); - CurrentUser().modify()->setAudioEncoding( getAudioEncodingValue(encodingRow)); - } - catch(Wt::Dbo::Exception& exception) - { - LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what(); - return false; - } - - return true; - } - - int getAudioEncodingRow( Database::AudioEncoding encoding) - { - for (int i = 0; i < _encodingModel->rowCount(); ++i) - { - if (getAudioEncodingValue(i) == encoding) - return i; - } - return -1; - } - - int getAudioEncodingRow( Wt::WString encoding) - { - for (int i = 0; i < _encodingModel->rowCount(); ++i) - { - if (getAudioEncodingString(i) == encoding) - return i; - } - return -1; - } - - Database::AudioEncoding getAudioEncodingValue(int row) - { - return boost::any_cast - (_encodingModel->data(_encodingModel->index(row, 0), Wt::UserRole)); - } - - Wt::WString getAudioEncodingString(int row) - { - return boost::any_cast - (_encodingModel->data(_encodingModel->index(row, 0), Wt::DisplayRole)); - } - - - private: - void initializeModels() - { - Wt::Dbo::Transaction transaction(DboSession()); - - _bitrateModel = new Wt::WStringListModel(this); - BOOST_FOREACH(std::size_t bitrate, Database::User::audioBitrates) - { - if (bitrate <= CurrentUser()->getMaxAudioBitrate()) - _bitrateModel->addString( Wt::WString("{1}").arg( bitrate / 1000 ) ); // in kbps - } - - _encodingModel = new Wt::WStringListModel(this); - int row = 0; - BOOST_FOREACH(Database::AudioEncoding encoding, Database::User::audioEncodings) - { - _encodingModel->addString(encodingToString(encoding)); - _encodingModel->setData(row, 0, encoding, Wt::UserRole); - row++; - } - } - - std::string _userId; - Wt::WStringListModel* _bitrateModel; - Wt::WStringListModel* _encodingModel; -}; - -const Wt::WFormModel::Field AudioFormModel::BitrateField = "bitrate"; -const Wt::WFormModel::Field AudioFormModel::EncodingField = "encoding"; - -AudioFormView::AudioFormView(Wt::WContainerWidget *parent) -: Wt::WTemplateFormView(parent) -{ - - _model = new AudioFormModel(this); - - setTemplateText(tr("settings-audio")); - addFunction("id", &WTemplate::Functions::id); - addFunction("block", &WTemplate::Functions::id); - - _applyInfo = new Wt::WText(); - _applyInfo->setInline(false); - _applyInfo->hide(); - bindWidget("apply-info", _applyInfo); - - // Bitrate - Wt::WComboBox *bitrateCB = new Wt::WComboBox(); - setFormWidget(AudioFormModel::BitrateField, bitrateCB); - bitrateCB->setStyleClass("span2"); - bitrateCB->setModel(_model->bitrateModel()); - bitrateCB->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Encoding - Wt::WComboBox *encodingCB = new Wt::WComboBox(); - setFormWidget(AudioFormModel::EncodingField, encodingCB); - encodingCB->setModel(_model->encodingModel()); - encodingCB->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Title & Buttons - bindString("title", "Audio settings"); - - Wt::WPushButton *saveButton = new Wt::WPushButton("Apply"); - saveButton->setStyleClass("btn-primary"); - bindWidget("save-button", saveButton); - saveButton->clicked().connect(this, &AudioFormView::processSave); - - Wt::WPushButton *cancelButton = new Wt::WPushButton("Discard"); - bindWidget("cancel-button", cancelButton); - cancelButton->clicked().connect(this, &AudioFormView::processCancel); - - updateView(_model); -} - -void -AudioFormView::processCancel() -{ - _applyInfo->show(); - - _applyInfo->setText( Wt::WString::fromUTF8("Parameters reverted!")); - _applyInfo->setStyleClass("alert alert-info"); - _model->loadData(); - - _model->validate(); - updateView(_model); -} - -void -AudioFormView::processSave() -{ - updateModel(_model); - - _applyInfo->show(); - if (_model->validate()) - { - Wt::WString error; - // commit model into DB - if (_model->saveData(error) ) - { - _applyInfo->setText( Wt::WString::fromUTF8("New parameters successfully applied!")); - _applyInfo->setStyleClass("alert alert-success"); - } - else - { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters: ") + error); - _applyInfo->setStyleClass("alert alert-danger"); - } - } - else - { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters!")); - _applyInfo->setStyleClass("alert alert-danger"); - } - updateView(_model); -} - -} // namespace Settings -} // namespace UserInterface diff --git a/src/ui/settings/SettingsAudioFormView.hpp b/src/ui/settings/SettingsAudioFormView.hpp deleted file mode 100644 index deed1ea6..00000000 --- a/src/ui/settings/SettingsAudioFormView.hpp +++ /dev/null @@ -1,49 +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 UI_SETTINGS_AUDIO_FORM_VIEW_HPP -#define UI_SETTINGS_AUDIO_ACCOUNT_FORM_VIEW_HPP - -#include -#include -#include - -namespace UserInterface { -namespace Settings { - -class AudioFormModel; - -class AudioFormView : public Wt::WTemplateFormView -{ - public: - AudioFormView(Wt::WContainerWidget *parent = 0); - - private: - void processCancel(); // reload from DB - void processSave(); // commit into DB - - AudioFormModel* _model; - Wt::WText* _applyInfo; - -}; - -} // namespace Settings -} // namespace UserInterface - -#endif diff --git a/src/ui/settings/SettingsDatabaseFormView.cpp b/src/ui/settings/SettingsDatabaseFormView.cpp deleted file mode 100644 index 1379d0d1..00000000 --- a/src/ui/settings/SettingsDatabaseFormView.cpp +++ /dev/null @@ -1,400 +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 -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "utils/Logger.hpp" - -#include "database/Setting.hpp" - -#include "common/DirectoryValidator.hpp" -#include "LmsApplication.hpp" - -#include "SettingsDatabaseFormView.hpp" - -namespace UserInterface { -namespace Settings { - -using namespace Database; - -class DatabaseFormModel : public Wt::WFormModel -{ - public: - // Associate each field with a unique string literal. - static const Field UpdatePeriodField; - static const Field UpdateStartTimeField; - static const Field AudioFileExtensionsField; - static const Field VideoFileExtensionsField; - static const Field TagsHighLevelAcousticBrainz; - static const Field TagsHighLevelAcousticBrainzMinProbability; - static const Field TagsSimilarityAcousticBrainz; - - DatabaseFormModel(Wt::WObject *parent = 0) - : Wt::WFormModel(parent) - { - initializeModels(); - - addField(UpdatePeriodField); - addField(UpdateStartTimeField); - addField(AudioFileExtensionsField); - addField(VideoFileExtensionsField); - addField(TagsHighLevelAcousticBrainz); - addField(TagsHighLevelAcousticBrainzMinProbability); - addField(TagsSimilarityAcousticBrainz); - - setValidator(UpdatePeriodField, createUpdatePeriodValidator()); - setValidator(UpdateStartTimeField, createStartTimeValidator()); - setValidator(AudioFileExtensionsField, createFileExtensionValidator()); - setValidator(VideoFileExtensionsField, createFileExtensionValidator()); - setValidator(TagsHighLevelAcousticBrainzMinProbability, createMinProbabilityValidator()); - - // populate the model with initial data - loadData(); - } - - Wt::WAbstractItemModel *updatePeriodModel() { return _updatePeriodModel; } - Wt::WAbstractItemModel *updateStartTimeModel() { return _updateStartTimeModel; } - - void loadData() - { - int periodRow = getUpdatePeriodModelRow( Setting::getString(DboSession(), "update_period")); - if (periodRow != -1) - setValue(UpdatePeriodField, updatePeriodDisplay(periodRow)); - - int startTimeRow = getUpdateStartTimeModelRow( Setting::getDuration(DboSession(), "update_start_time") ); - if (startTimeRow != -1) - setValue(UpdateStartTimeField, updateStartTime( startTimeRow ) ); - - setValue(AudioFileExtensionsField, Setting::getString(DboSession(), "audio_file_extensions")); - setValue(VideoFileExtensionsField, Setting::getString(DboSession(), "video_file_extensions")); - - setValue(TagsHighLevelAcousticBrainz, Setting::getBool(DboSession(), "tags_highlevel_acousticbrainz")); - setValue(TagsHighLevelAcousticBrainzMinProbability, Setting::getInt(DboSession(), "tags_highlevel_acousticbrainz_min_probability")); - setValue(TagsSimilarityAcousticBrainz, Setting::getBool(DboSession(), "tags_similarity_acousticbrainz")); - } - - void saveData() - { - int periodRow = getUpdatePeriodModelRow( boost::any_cast(value(UpdatePeriodField))); - assert(periodRow != -1); - Setting::setString(DboSession(), "update_period", updatePeriodSetting( periodRow ) ); - - int startTimeRow = getUpdateStartTimeModelRow( boost::any_cast(value(UpdateStartTimeField))); - assert(startTimeRow != -1); - Setting::setDuration(DboSession(), "update_start_time", updateStartTimeDuration( startTimeRow ) ); - - Setting::setString(DboSession(), "audio_file_extensions", boost::any_cast(value(AudioFileExtensionsField)).toUTF8()); - Setting::setString(DboSession(), "video_file_extensions", boost::any_cast(value(VideoFileExtensionsField)).toUTF8()); - - Setting::setBool(DboSession(), "tags_highlevel_acousticbrainz", boost::any_cast(value(TagsHighLevelAcousticBrainz))); - Setting::setInt(DboSession(), "tags_highlevel_acousticbrainz_min_probability", std::stoi(boost::any_cast(value(TagsHighLevelAcousticBrainzMinProbability)).toUTF8())); - Setting::setBool(DboSession(), "tags_similarity_acousticbrainz", boost::any_cast(value(TagsSimilarityAcousticBrainz))); - } - - void setImmediateScan() - { - Setting::setBool(DboSession(), "manual_scan_requested", true); - } - - int getUpdatePeriodModelRow(Wt::WString value) - { - for (int i = 0; i < _updatePeriodModel->rowCount(); ++i) - { - if (updatePeriodDisplay(i) == value) - return i; - } - return -1; - } - - int getUpdatePeriodModelRow(std::string value) - { - for (int i = 0; i < _updatePeriodModel->rowCount(); ++i) - { - if (updatePeriodSetting(i) == value) - return i; - } - - return -1; - } - - std::string updatePeriodSetting(int row) { - return boost::any_cast - (_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::UserRole)); - } - - Wt::WString updatePeriodDisplay(int row) { - return boost::any_cast - (_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::DisplayRole)); - } - - - int getUpdateStartTimeModelRow(Wt::WString value) - { - for (int i = 0; i < _updateStartTimeModel->rowCount(); ++i) - { - if (updateStartTime(i) == value) - return i; - } - - return -1; - } - - int getUpdateStartTimeModelRow(boost::posix_time::time_duration duration) - { - for (int i = 0; i < _updateStartTimeModel->rowCount(); ++i) - { - if (updateStartTimeDuration(i) == duration) - return i; - } - - return -1; - } - - boost::posix_time::time_duration updateStartTimeDuration(int row) { - return boost::any_cast - (_updateStartTimeModel->data(_updateStartTimeModel->index(row, 0), Wt::UserRole)); - } - - Wt::WString updateStartTime(int row) { - return boost::any_cast - (_updateStartTimeModel->data(_updateStartTimeModel->index(row, 0), Wt::DisplayRole)); - } - - - private: - - void initializeModels() { - - _updatePeriodModel = new Wt::WStringListModel(this); - - _updatePeriodModel->addString("Never"); - _updatePeriodModel->setData(0, 0, std::string("never"), Wt::UserRole); - - _updatePeriodModel->addString("Daily"); - _updatePeriodModel->setData(1, 0, std::string("daily"), Wt::UserRole); - - _updatePeriodModel->addString("Weekly"); - _updatePeriodModel->setData(2, 0, std::string("weekly"), Wt::UserRole); - - _updatePeriodModel->addString("Monthly"); - _updatePeriodModel->setData(3, 0, std::string("monthly"), Wt::UserRole); - - _updateStartTimeModel = new Wt::WStringListModel(this); - - for (std::size_t i = 0; i < 24; ++i) - { - boost::posix_time::time_duration dur = boost::posix_time::hours(i); - - boost::posix_time::time_facet* facet = new boost::posix_time::time_facet("%H:%M"); - facet->time_duration_format("%H:%M"); - - std::ostringstream oss; - oss.imbue(std::locale(oss.getloc(), facet)); - - oss << dur; - - _updateStartTimeModel->addString( oss.str() ); - _updateStartTimeModel->setData(i, 0, dur, Wt::UserRole); - } - - } - - Wt::WValidator *createUpdatePeriodValidator() - { - Wt::WValidator* v = new Wt::WValidator(); - v->setMandatory(true); - return v; - } - - Wt::WValidator *createStartTimeValidator() - { - Wt::WValidator* v = new Wt::WValidator(); - v->setMandatory(true); - return v; - } - - Wt::WValidator *createFileExtensionValidator() - { - Wt::WRegExpValidator *v = new Wt::WRegExpValidator("(?:\\.\\w+(?:\\s*))+"); - return v; - } - - Wt::WValidator *createMinProbabilityValidator() - { - Wt::WIntValidator *v = new Wt::WIntValidator(50, 100); - v->setMandatory(true); - return v; - } - - Wt::WStringListModel* _updatePeriodModel; - Wt::WStringListModel* _updateStartTimeModel; - -}; - -const Wt::WFormModel::Field DatabaseFormModel::UpdatePeriodField = "update-period"; -const Wt::WFormModel::Field DatabaseFormModel::UpdateStartTimeField = "update-start-time"; -const Wt::WFormModel::Field DatabaseFormModel::AudioFileExtensionsField = "audio-file-extensions"; -const Wt::WFormModel::Field DatabaseFormModel::VideoFileExtensionsField = "video-file-extensions"; -const Wt::WFormModel::Field DatabaseFormModel::TagsHighLevelAcousticBrainz = "tags-highlevel-acousticbrainz"; -const Wt::WFormModel::Field DatabaseFormModel::TagsHighLevelAcousticBrainzMinProbability = "tags-highlevel-acousticbrainz-min-probability"; -const Wt::WFormModel::Field DatabaseFormModel::TagsSimilarityAcousticBrainz = "tags-similarity-acousticbrainz"; - - -DatabaseFormView::DatabaseFormView(Wt::WContainerWidget *parent) -: Wt::WTemplateFormView(parent) -{ - _model = new DatabaseFormModel(this); - - setTemplateText(tr("settings-database")); - addFunction("id", &WTemplate::Functions::id); - addFunction("block", &WTemplate::Functions::id); - - _applyInfo = new Wt::WText(); - _applyInfo->setInline(false); - _applyInfo->hide(); - bindWidget("apply-info", _applyInfo); - - // Update Period - Wt::WComboBox *updatePeriodCB = new Wt::WComboBox(); - setFormWidget(DatabaseFormModel::UpdatePeriodField, updatePeriodCB); - updatePeriodCB->setModel(_model->updatePeriodModel()); - updatePeriodCB->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Update Start Time - Wt::WComboBox *updateStartTimeCB = new Wt::WComboBox(); - setFormWidget(DatabaseFormModel::UpdateStartTimeField, updateStartTimeCB); - updateStartTimeCB->setModel(_model->updateStartTimeModel()); - updateStartTimeCB->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Audio file extensions - Wt::WLineEdit *audioFileExtensionsEdit = new Wt::WLineEdit(); - setFormWidget(DatabaseFormModel::AudioFileExtensionsField, audioFileExtensionsEdit); - audioFileExtensionsEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Video file extensions - Wt::WLineEdit *videoFileExtensionsEdit = new Wt::WLineEdit(); - setFormWidget(DatabaseFormModel::VideoFileExtensionsField, videoFileExtensionsEdit); - videoFileExtensionsEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Tags from AB high level - Wt::WCheckBox* highLevel = new Wt::WCheckBox(); - setFormWidget(DatabaseFormModel::TagsHighLevelAcousticBrainz, highLevel); - - setFormWidget(DatabaseFormModel::TagsHighLevelAcousticBrainzMinProbability, new Wt::WSpinBox()); - - highLevel->changed().connect(std::bind([=] { - _model->setReadOnly(DatabaseFormModel::TagsHighLevelAcousticBrainzMinProbability, - !(highLevel->checkState() == Wt::Checked)); - updateModel(_model); - updateViewField(_model, DatabaseFormModel::TagsHighLevelAcousticBrainzMinProbability); - })); - - // Tags from AB similarity - setFormWidget(DatabaseFormModel::TagsSimilarityAcousticBrainz, new Wt::WCheckBox()); - - // Title & Buttons - bindString("scan-title", "Scan settings"); - bindString("tags-title", "Tag settings"); - - Wt::WPushButton *saveButton = new Wt::WPushButton("Apply"); - bindWidget("apply-button", saveButton); - saveButton->setStyleClass("btn-primary"); - - Wt::WPushButton *discardButton = new Wt::WPushButton("Discard"); - bindWidget("discard-button", discardButton); - - saveButton->clicked().connect(this, &DatabaseFormView::processSave); - discardButton->clicked().connect(this, &DatabaseFormView::processDiscard); - - Wt::WPushButton *immediateScanButton = new Wt::WPushButton("Immediate scan"); - immediateScanButton->setStyleClass("btn-warning"); - bindWidget("immediate-scan-button", immediateScanButton); - immediateScanButton->clicked().connect(this, &DatabaseFormView::processImmediateScan); - - updateView(_model); - -} - -void -DatabaseFormView::processImmediateScan() -{ - - _model->setImmediateScan(); - - _applyInfo->setText( Wt::WString::fromUTF8("Media folder scan has been started!" ) ); - _applyInfo->setStyleClass("alert alert-warning"); - _applyInfo->show(); - - _sigChanged.emit(); -} - -void -DatabaseFormView::processDiscard() -{ - _applyInfo->show(); - - _applyInfo->setText( Wt::WString::fromUTF8("Parameters reverted!")); - _applyInfo->setStyleClass("alert alert-info"); - - _model->loadData(); - - _model->validate(); - updateView(_model); -} - -void -DatabaseFormView::processSave() -{ - updateModel(_model); - - _applyInfo->show(); - - if (_model->validate()) { - // Make the model to commit data into DB - _model->saveData(); - - _sigChanged.emit(); - - _applyInfo->setText( Wt::WString::fromUTF8("New parameters successfully applied!")); - _applyInfo->setStyleClass("alert alert-success"); - } - else { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters!")); - _applyInfo->setStyleClass("alert alert-danger"); - } - // Udate the view: Delete any validation message in the view, etc. - updateView(_model); -} - - -} // namespace Settings -} // namespace UserInterface - - diff --git a/src/ui/settings/SettingsDatabaseFormView.hpp b/src/ui/settings/SettingsDatabaseFormView.hpp deleted file mode 100644 index 23585fdb..00000000 --- a/src/ui/settings/SettingsDatabaseFormView.hpp +++ /dev/null @@ -1,58 +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 UI_SETTINGS_DB_FORM_VIEW_HPP -#define UI_SETTINGS_DB_FORM_VIEW_HPP - -#include -#include -#include -#include - -namespace UserInterface { -namespace Settings { - -class DatabaseFormModel; - -class DatabaseFormView : public Wt::WTemplateFormView -{ - public: - DatabaseFormView(Wt::WContainerWidget *parent = 0); - - Wt::Signal& changed() { return _sigChanged; } - - private: - - Wt::Signal _sigChanged; - - void processSave(); - void processDiscard(); - void processImmediateScan(); - - Wt::WText *_applyInfo; - DatabaseFormModel *_model; - -}; - - -} // namespace Settings -} // namespace UserInterface - -#endif - diff --git a/src/ui/settings/SettingsFirstConnectionFormView.cpp b/src/ui/settings/SettingsFirstConnectionFormView.cpp deleted file mode 100644 index c6873079..00000000 --- a/src/ui/settings/SettingsFirstConnectionFormView.cpp +++ /dev/null @@ -1,229 +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 -#include -#include -#include - -#include "utils/Logger.hpp" - -#include "common/Validators.hpp" -#include "LmsApplication.hpp" - -#include "SettingsFirstConnectionFormView.hpp" - -namespace UserInterface { -namespace Settings { - -class FirstConnectionFormModel : public Wt::WFormModel -{ - public: - - // Associate each field with a unique string literal. - static const Field NameField; - static const Field EmailField; - static const Field PasswordField; - static const Field PasswordConfirmField; - - FirstConnectionFormModel(Wt::WObject *parent = 0) - : Wt::WFormModel(parent) - { - addField(NameField); - addField(EmailField); - addField(PasswordField); - addField(PasswordConfirmField); - - setValidator(NameField, createNameValidator()); - setValidator(EmailField, createEmailValidator()); - - } - - bool saveData(Wt::WString& error) - { - // DBO transaction active here - try { - Wt::Dbo::Transaction transaction(DboSession()); - - // Check if a user already exist - // If it's the case, just do nothing - if (!Database::User::getAll(DboSession()).empty()) - { - LMS_LOG(UI, ERROR) << "Admin user already created"; - error = Wt::WString("Admin user already created!"); - return false; - } - - // Create user - Wt::Auth::User authUser = DbHandler().getUserDatabase().registerNew(); - Database::User::pointer user = DbHandler().getUser(authUser); - - // Account - authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(NameField)); - authUser.setEmail(valueText(EmailField).toUTF8()); - Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); - - user.modify()->setAdmin( true ); - - } - catch(Wt::Dbo::Exception& exception) - { - LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what(); - error = Wt::WString(exception.what()); - return false; - } - - return true; - } - - bool validateField(Field field) - { - // DBO transaction active here - - Wt::WString error; - - if (field == PasswordField) - { - // Password is mandatory if we create the user - if (!valueText(PasswordField).empty()) - { - // Evaluate the strength of the password - Wt::Auth::AbstractPasswordService::StrengthValidatorResult res - = Database::Handler::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), - valueText(NameField), - valueText(EmailField).toUTF8()); - - if (!res.isValid()) - error = res.message(); - } - else - return Wt::WFormModel::validateField(field); - } - else if (field == PasswordConfirmField) - { - if (validation(PasswordField).state() == Wt::WValidator::Valid) - { - if (valueText(PasswordField) != valueText(PasswordConfirmField)) - error = Wt::WString::tr("Wt.Auth.passwords-dont-match"); - } - } - else - { - // Apply validators - return Wt::WFormModel::validateField(field); - } - - setValidation(field, Wt::WValidator::Result( error.empty() ? Wt::WValidator::Valid : Wt::WValidator::Invalid, error)); - - return validation(field).state() == Wt::WValidator::Valid; - } - - - private: - -}; - -const Wt::WFormModel::Field FirstConnectionFormModel::NameField = "name"; -const Wt::WFormModel::Field FirstConnectionFormModel::EmailField = "email"; -const Wt::WFormModel::Field FirstConnectionFormModel::PasswordField = "password"; -const Wt::WFormModel::Field FirstConnectionFormModel::PasswordConfirmField = "password-confirm"; - -FirstConnectionFormView::FirstConnectionFormView(Wt::WContainerWidget *parent) -: Wt::WTemplateFormView(parent) -{ - - _model = new FirstConnectionFormModel(this); - - setTemplateText(tr("settings-first-connection")); - addFunction("id", &WTemplate::Functions::id); - addFunction("block", &WTemplate::Functions::id); - - _applyInfo = new Wt::WText(); - _applyInfo->setInline(false); - _applyInfo->hide(); - bindWidget("apply-info", _applyInfo); - - // Name - Wt::WLineEdit* accountEdit = new Wt::WLineEdit(); - setFormWidget(FirstConnectionFormModel::NameField, accountEdit); - accountEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Email - Wt::WLineEdit* emailEdit = new Wt::WLineEdit(); - setFormWidget(FirstConnectionFormModel::EmailField, emailEdit); - emailEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Password - Wt::WLineEdit* passwordEdit = new Wt::WLineEdit(); - setFormWidget(FirstConnectionFormModel::PasswordField, passwordEdit ); - passwordEdit->setEchoMode(Wt::WLineEdit::Password); - passwordEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Password confirmation - Wt::WLineEdit* passwordConfirmEdit = new Wt::WLineEdit(); - setFormWidget(FirstConnectionFormModel::PasswordConfirmField, passwordConfirmEdit); - passwordConfirmEdit->setEchoMode(Wt::WLineEdit::Password); - passwordConfirmEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Title & Buttons - bindString("title", "Create Admin account"); - - _saveButton = new Wt::WPushButton("Create"); - _saveButton->setStyleClass("btn-primary"); - bindWidget("save-button", _saveButton); - _saveButton->clicked().connect(this, &FirstConnectionFormView::processSave); - - updateView(_model); - -} - -void -FirstConnectionFormView::processSave() -{ - updateModel(_model); - - _applyInfo->show(); - if (_model->validate()) - { - Wt::WString error; - // commit model into DB - if (_model->saveData(error) ) - { - _applyInfo->setText( Wt::WString::fromUTF8("New parameters successfully applied! Please refresh this page in order to login")); - _applyInfo->setStyleClass("alert alert-success"); - _saveButton->hide(); - } - else - { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters: ") + error); - _applyInfo->setStyleClass("alert alert-danger"); - } - } - else - { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters!")); - _applyInfo->setStyleClass("alert alert-danger"); - } - updateView(_model); -} - -} // namespace Settings -} // namespace UserInterface diff --git a/src/ui/settings/SettingsFirstConnectionFormView.hpp b/src/ui/settings/SettingsFirstConnectionFormView.hpp deleted file mode 100644 index 1761c192..00000000 --- a/src/ui/settings/SettingsFirstConnectionFormView.hpp +++ /dev/null @@ -1,52 +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 UI_SETTINGS_FIRST_CONNECTION_FORM_VIEW_HPP -#define UI_SETTINGS_FIRST_CONNECTION_FORM_VIEW_HPP - -#include -#include -#include -#include - -namespace UserInterface { -namespace Settings { - -class FirstConnectionFormModel; - -class FirstConnectionFormView : public Wt::WTemplateFormView -{ - public: - FirstConnectionFormView(Wt::WContainerWidget *parent = 0); - - private: - - void processSave(); - - Wt::WPushButton* _saveButton; - FirstConnectionFormModel* _model; - Wt::WText* _applyInfo; - -}; - - -} // namespace Settings -} // namespace UserInterface - -#endif diff --git a/src/ui/settings/SettingsMediaDirectories.cpp b/src/ui/settings/SettingsMediaDirectories.cpp deleted file mode 100644 index d9b75a20..00000000 --- a/src/ui/settings/SettingsMediaDirectories.cpp +++ /dev/null @@ -1,172 +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 -#include -#include - -#include "database/MediaDirectory.hpp" - -#include "LmsApplication.hpp" - -#include "SettingsMediaDirectoryFormView.hpp" - -#include "SettingsMediaDirectories.hpp" - -namespace UserInterface { -namespace Settings { - -MediaDirectories::MediaDirectories(Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent) -{ - // Stack two widgets: - _stack = new Wt::WStackedWidget(this); - - // 1/ the media directory table view - { - Wt::WGroupBox *container = new Wt::WGroupBox("Media Folders", _stack); - - _table = new Wt::WTable(container); - - _table->addStyleClass("table form-inline"); - - _table->toggleStyleClass("table-hover", true); - _table->toggleStyleClass("table-striped", true); - - _table->setHeaderCount(1); - - _table->elementAt(0, 0)->addWidget(new Wt::WText("#")); - _table->elementAt(0, 1)->addWidget(new Wt::WText("Path")); - _table->elementAt(0, 2)->addWidget(new Wt::WText("Type")); - - Wt::WPushButton* addBtn = new Wt::WPushButton("Add Folder"); - addBtn->setStyleClass("btn-success"); - container->addWidget( addBtn ); - addBtn->clicked().connect(boost::bind(&MediaDirectories::handleCreateMediaDirectory, this)); - } - - refresh(); -} - -void -MediaDirectories::refresh(void) -{ - - assert(_table->rowCount() > 0); - for (int i = _table->rowCount() - 1; i > 0; --i) - _table->deleteRow(i); - - Wt::Dbo::Transaction transaction(DboSession()); - - std::vector mediaDirectories = Database::MediaDirectory::getAll(DboSession()); - - std::size_t id = 1; - BOOST_FOREACH(Database::MediaDirectory::pointer mediaDirectory, mediaDirectories) - { - _table->elementAt(id, 0)->addWidget(new Wt::WText( Wt::WString::fromUTF8("{1}").arg(id))); - _table->elementAt(id, 1)->addWidget(new Wt::WText( Wt::WString::fromUTF8(mediaDirectory->getPath().string()) )); - - Wt::WString dirType; - switch(mediaDirectory->getType()) - { - case Database::MediaDirectory::Video: dirType = "Video"; break; - case Database::MediaDirectory::Audio: dirType = "Audio"; break; - } - - _table->elementAt(id, 2)->addWidget( new Wt::WText( dirType )); - - Wt::WPushButton* delBtn = new Wt::WPushButton("Delete"); - delBtn->setStyleClass("btn-danger"); - _table->elementAt(id, 3)->addWidget(delBtn); - delBtn->clicked().connect(boost::bind( &MediaDirectories::handleDelMediaDirectory, this, mediaDirectory->getPath(), mediaDirectory->getType() ) ); - - ++id; - } -} - -void -MediaDirectories::handleDelMediaDirectory(boost::filesystem::path p, Database::MediaDirectory::Type type) -{ - Wt::WMessageBox *messageBox = new Wt::WMessageBox - ("Delete Folder", - Wt::WString( "Deleting folder '{1}'?").arg(p.string()), - Wt::Question, Wt::Yes | Wt::No); - - messageBox->setModal(true); - - messageBox->buttonClicked().connect(std::bind([=] () { - if (messageBox->buttonResult() == Wt::Yes) - { - { - Wt::Dbo::Transaction transaction(DboSession()); - - // Delete the media diretory - Database::MediaDirectory::pointer mediaDirectory = Database::MediaDirectory::get(DboSession(), p, type); - if (mediaDirectory) - mediaDirectory.remove(); - } - - refresh(); - - // Emit something changed in the settings - _sigChanged.emit(); - } - - delete messageBox; - })); - - messageBox->show(); - -} - -void -MediaDirectories::handleCreateMediaDirectory(void) -{ - assert(_stack->count() == 1); - - MediaDirectoryFormView* formView = new MediaDirectoryFormView(_stack); - formView->completed().connect(this, &MediaDirectories::handleMediaDirectoryFormCompleted); - - _stack->setCurrentIndex(1); -} - -void -MediaDirectories::handleMediaDirectoryFormCompleted(bool changed) -{ - _stack->setCurrentIndex(0); - - if (changed) - { - // Refresh the user table if a change has been made - refresh(); - - // Emit something changed in the settings - _sigChanged.emit(); - } - - // Delete the form view - delete _stack->widget(1); - -} - -} // namespace UserInterface -} // namespace Settings - - diff --git a/src/ui/settings/SettingsMediaDirectories.hpp b/src/ui/settings/SettingsMediaDirectories.hpp deleted file mode 100644 index 4fb2bdf8..00000000 --- a/src/ui/settings/SettingsMediaDirectories.hpp +++ /dev/null @@ -1,59 +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 UI_SETTINGS_MEDIA_DIRECTORIES_HPP -#define UI_SETTINGS_MEDIA_DIRECTORIES_HPP - -#include -#include -#include -#include - -#include "database/MediaDirectory.hpp" - -namespace UserInterface { -namespace Settings { - -class MediaDirectories : public Wt::WContainerWidget -{ - public: - MediaDirectories(Wt::WContainerWidget *parent = 0); - - void refresh(); - - Wt::Signal& changed() { return _sigChanged; } - - private: - - Wt::Signal _sigChanged; - - void handleMediaDirectoryFormCompleted(bool changed); - - void handleDelMediaDirectory(boost::filesystem::path p, Database::MediaDirectory::Type type); - void handleCreateMediaDirectory(void); - - Wt::WStackedWidget* _stack; - Wt::WTable* _table; -}; - -} // namespace UserInterface -} // namespace Settings - -#endif - diff --git a/src/ui/settings/SettingsMediaDirectoryFormView.cpp b/src/ui/settings/SettingsMediaDirectoryFormView.cpp deleted file mode 100644 index a9e4a2f9..00000000 --- a/src/ui/settings/SettingsMediaDirectoryFormView.cpp +++ /dev/null @@ -1,185 +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 -#include -#include -#include -#include - -#include "utils/Logger.hpp" - -#include "database/MediaDirectory.hpp" - -#include "common/DirectoryValidator.hpp" -#include "LmsApplication.hpp" - -#include "SettingsMediaDirectoryFormView.hpp" - -namespace UserInterface { -namespace Settings { - -// TODO validate if directory already exists -class MediaDirectoryFormModel : public Wt::WFormModel -{ - public: - - // Associate each field with a unique string literal. - static const Field PathField; - static const Field TypeField; - - MediaDirectoryFormModel(Wt::WObject *parent = 0) - : Wt::WFormModel(parent) - { - initializeModels(); - - addField(PathField); - addField(TypeField); - - DirectoryValidator* dirValidator = new DirectoryValidator(); - dirValidator->setMandatory(true); - setValidator(PathField, dirValidator); - setValidator(TypeField, new Wt::WValidator(true)); // mandatory - - } - - Wt::WAbstractItemModel *typeModel() { return _typeModel; } - - bool saveData(Wt::WString& error) - { - try { - Wt::Dbo::Transaction transaction(DboSession()); - - Database::MediaDirectory::Type type - = (valueText(TypeField) == "Audio") ? Database::MediaDirectory::Audio : Database::MediaDirectory::Video; - - if (Database::MediaDirectory::get(DboSession(), valueText(PathField).toUTF8(), type)) - { - error = "This Path/Type already exists!"; - return false; - } - - Database::MediaDirectory::create(DboSession(), valueText(PathField).toUTF8(), type); - - } - catch(Wt::Dbo::Exception& exception) - { - LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what(); - return false; - } - - return true; - } - - private: - void initializeModels() - { - _typeModel = new Wt::WStringListModel(this); - _typeModel->addString("Audio"); - _typeModel->addString("Video"); - } - - std::string _userId; - Wt::WStringListModel* _typeModel; -}; - -const Wt::WFormModel::Field MediaDirectoryFormModel::PathField = "path"; -const Wt::WFormModel::Field MediaDirectoryFormModel::TypeField = "type"; - -MediaDirectoryFormView::MediaDirectoryFormView(Wt::WContainerWidget *parent) -: Wt::WTemplateFormView(parent) -{ - - _model = new MediaDirectoryFormModel(this); - - setTemplateText(tr("settings-new-media-directory")); - addFunction("id", &WTemplate::Functions::id); - addFunction("block", &WTemplate::Functions::id); - - _applyInfo = new Wt::WText(); - _applyInfo->setInline(false); - _applyInfo->hide(); - bindWidget("apply-info", _applyInfo); - - // Path - Wt::WLineEdit* pathEdit = new Wt::WLineEdit(); - setFormWidget(MediaDirectoryFormModel::PathField, pathEdit); - pathEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Type - Wt::WComboBox *typeCB = new Wt::WComboBox(); - setFormWidget(MediaDirectoryFormModel::TypeField, typeCB); - typeCB->setModel(_model->typeModel()); - typeCB->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Title & Buttons - bindString("title", "Add media folder"); - - Wt::WPushButton *saveButton = new Wt::WPushButton("Add"); - saveButton->setStyleClass("btn-primary"); - bindWidget("save-button", saveButton); - saveButton->clicked().connect(this, &MediaDirectoryFormView::processSave); - - Wt::WPushButton *cancelButton = new Wt::WPushButton("Cancel"); - bindWidget("cancel-button", cancelButton); - cancelButton->clicked().connect(this, &MediaDirectoryFormView::processCancel); - - updateView(_model); -} - -void -MediaDirectoryFormView::processCancel() -{ - // parent widget will delete this widget - completed().emit(false); -} - -void -MediaDirectoryFormView::processSave() -{ - updateModel(_model); - - _applyInfo->show(); - if (_model->validate()) - { - Wt::WString error; - // commit model into DB - if (_model->saveData(error) ) { - completed().emit(true); - return; - } - else - { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters: ") + error); - _applyInfo->setStyleClass("alert alert-danger"); - } - } - else - { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters!")); - _applyInfo->setStyleClass("alert alert-danger"); - } - - updateView(_model); -} - -} // namespace Settings -} // namespace UserInterface diff --git a/src/ui/settings/SettingsMediaDirectoryFormView.hpp b/src/ui/settings/SettingsMediaDirectoryFormView.hpp deleted file mode 100644 index 29554c74..00000000 --- a/src/ui/settings/SettingsMediaDirectoryFormView.hpp +++ /dev/null @@ -1,54 +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 UI_SETTINGS_MEDIA_DIR_FORM_VIEW_HPP -#define UI_SETTINGS_MEDIA_DIR_FORM_VIEW_HPP - -#include -#include -#include -#include - -namespace UserInterface { -namespace Settings { - -class MediaDirectoryFormModel; - -class MediaDirectoryFormView : public Wt::WTemplateFormView -{ - public: - MediaDirectoryFormView(Wt::WContainerWidget *parent = 0); - - // Signal emitted once the form is completed - Wt::Signal& completed() { return _sigCompleted; } - - private: - void processCancel(); // reload from DB - void processSave(); // commit into DB - - Wt::Signal _sigCompleted; - MediaDirectoryFormModel* _model; - Wt::WText* _applyInfo; - -}; - -} // namespace Settings -} // namespace UserInterface - -#endif diff --git a/src/ui/settings/SettingsUserFormView.cpp b/src/ui/settings/SettingsUserFormView.cpp deleted file mode 100644 index fc3224dd..00000000 --- a/src/ui/settings/SettingsUserFormView.cpp +++ /dev/null @@ -1,411 +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 -#include -#include -#include -#include -#include -#include - -#include "utils/Logger.hpp" - -#include "common/Validators.hpp" -#include "LmsApplication.hpp" - -#include "SettingsUserFormView.hpp" - - -namespace UserInterface { -namespace Settings { - -class UserFormModel : public Wt::WFormModel -{ - public: - - // Associate each field with a unique string literal. - static const Field NameField; - static const Field EmailField; - static const Field PasswordField; - static const Field PasswordConfirmField; - static const Field AdminField; - static const Field AudioBitrateLimitField; - static const Field VideoBitrateLimitField; - - UserFormModel(std::string userId, Wt::WObject *parent = 0) - : Wt::WFormModel(parent), - _userId(userId) - { - initializeModels(); - - addField(NameField); - addField(EmailField); - addField(PasswordField); - addField(PasswordConfirmField); - addField(AdminField); - addField(AudioBitrateLimitField); - addField(VideoBitrateLimitField); - - setValidator(NameField, createNameValidator()); - setValidator(EmailField, createEmailValidator()); - // If creating a user, passwords are mandatory - if (_userId.empty()) - { - setValidator(PasswordField, new Wt::WValidator(true)); // mandatory - setValidator(PasswordConfirmField, new Wt::WValidator(true)); // mandatory - } - setValidator(AudioBitrateLimitField, new Wt::WValidator(true)); // mandatory - setValidator(VideoBitrateLimitField, new Wt::WValidator(true)); // mandatory - - // populate the model with initial data - loadData(userId); - } - - Wt::WAbstractItemModel *audioBitrateModel() { return _audioBitrateModel; } - Wt::WAbstractItemModel *videoBitrateModel() { return _videoBitrateModel; } - - void loadData(std::string userId) - { - - if (!userId.empty()) - { - Wt::Dbo::Transaction transaction(DboSession()); - - Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId( userId ); - Database::User::pointer user = DbHandler().getUser(authUser); - - Wt::Auth::User currentUser = CurrentAuthUser(); - - if (user && authUser.isValid()) - { - if (user->isAdmin()) - { - setValue(AdminField, true); - - // We can cannot remove admin rights to ourselves - if (currentUser == authUser) - setReadOnly(AdminField, true); - - // if the user is admin, no need to limit it - setReadOnly(AudioBitrateLimitField, true); - setValidator(AudioBitrateLimitField, nullptr); - - setReadOnly(VideoBitrateLimitField, true); - setValidator(VideoBitrateLimitField, nullptr); - } - else - { - setValue(AudioBitrateLimitField, user->getMaxAudioBitrate() / 1000); // in kbps - setValue(VideoBitrateLimitField, user->getMaxVideoBitrate() / 1000); // in kbps - } - - setValue(NameField, authUser.identity(Wt::Auth::Identity::LoginName)); - if (!authUser.email().empty()) - setValue(EmailField, authUser.email()); - else - setValue(EmailField, authUser.unverifiedEmail()); - } - } - } - - bool saveData() - { - try { - Wt::Dbo::Transaction transaction(DboSession()); - - if (_userId.empty()) - { - // Create user - Wt::Auth::User authUser = DbHandler().getUserDatabase().registerNew(); - Database::User::pointer user = DbHandler().getUser(authUser); - - // Account - authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(NameField)); - authUser.setEmail(valueText(EmailField).toUTF8()); - Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); - - // Access - { - boost::any v = value(AdminField); - if (!v.empty() && boost::any_cast(v) == true) - user.modify()->setAdmin( true ); - else - user.modify()->setAdmin( false ); - } - - if (!isReadOnly(AudioBitrateLimitField)) - user.modify()->setMaxAudioBitrate( Wt::asNumber(value(AudioBitrateLimitField)) * 1000); // in bps - - if (!isReadOnly(VideoBitrateLimitField)) - user.modify()->setMaxVideoBitrate( Wt::asNumber(value(VideoBitrateLimitField)) * 1000); // in bps - - transaction.commit(); - } - else - { - // Update user - Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId(_userId); - Database::User::pointer user = DbHandler().getUser( authUser ); - - // user may have been deleted by someone else - if (!authUser.isValid()) { - LMS_LOG(UI, ERROR) << "user identity does not exist!"; - return false; - } - else if(!user) - { - LMS_LOG(UI, ERROR) << "User not found!"; - return false; - } - - // Account - authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(NameField)); - authUser.setEmail(valueText(EmailField).toUTF8()); - - // Password - if (!valueText(PasswordField).empty()) - Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); - - // Access - if (!isReadOnly(AdminField)) - { - boost::any v = value(AdminField); - if (!v.empty() && boost::any_cast(v) == true) - user.modify()->setAdmin( true ); - else - user.modify()->setAdmin( false ); - } - - if (!isReadOnly(AudioBitrateLimitField)) - user.modify()->setMaxAudioBitrate( Wt::asNumber(value(AudioBitrateLimitField)) * 1000); // in bps - - if (!isReadOnly(VideoBitrateLimitField)) - user.modify()->setMaxVideoBitrate( Wt::asNumber(value(VideoBitrateLimitField)) * 1000); // in bps - - transaction.commit(); - } - } - catch(Wt::Dbo::Exception& exception) - { - LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what(); - return false; - } - - return true; - } - - bool validateField(Field field) - { - Wt::WString error; - - if (field == NameField) - { - Wt::Dbo::Transaction transaction(DboSession()); - // Must be unique since used as LoginIdentity - Wt::Auth::User user = DbHandler().getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, valueText(field)); - if (user.isValid() && user.id() != _userId) - error = "Already exists"; - else - return Wt::WFormModel::validateField(field); - } - else if (field == PasswordField) - { - // Password is mandatory if we create the user - if (!valueText(PasswordField).empty()) - { - // Evaluate the strength of the password - Wt::Auth::AbstractPasswordService::StrengthValidatorResult res - = Database::Handler::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), - valueText(NameField), - valueText(EmailField).toUTF8()); - - if (!res.isValid()) - error = res.message(); - } - else - return Wt::WFormModel::validateField(field); - } - else if (field == PasswordConfirmField) - { - if (validation(PasswordField).state() == Wt::WValidator::Valid) - { - if (valueText(PasswordField) != valueText(PasswordConfirmField)) - error = Wt::WString::tr("Wt.Auth.passwords-dont-match"); - } - } - else - { - // Apply validators - return Wt::WFormModel::validateField(field); - } - - setValidation(field, Wt::WValidator::Result( error.empty() ? Wt::WValidator::Valid : Wt::WValidator::Invalid, error)); - - return validation(field).state() == Wt::WValidator::Valid; - } - - - private: - - void initializeModels() - { - // AUDIO - _audioBitrateModel = new Wt::WStringListModel(this); - BOOST_FOREACH(std::size_t bitrate, Database::User::audioBitrates) - _audioBitrateModel->addString( Wt::WString("{1}").arg( bitrate / 1000 ) ); // in kbps - - // VIDEO - _videoBitrateModel = new Wt::WStringListModel(this); - BOOST_FOREACH(std::size_t bitrate, Database::User::videoBitrates) - _videoBitrateModel->addString( Wt::WString("{1}").arg( bitrate / 1000 ) ); // in kbps - - } - - std::string _userId; - Wt::WStringListModel* _audioBitrateModel; - Wt::WStringListModel* _videoBitrateModel; -}; - -const Wt::WFormModel::Field UserFormModel::NameField = "name"; -const Wt::WFormModel::Field UserFormModel::EmailField = "email"; -const Wt::WFormModel::Field UserFormModel::PasswordField = "password"; -const Wt::WFormModel::Field UserFormModel::PasswordConfirmField = "password-confirm"; -const Wt::WFormModel::Field UserFormModel::AdminField = "admin"; -const Wt::WFormModel::Field UserFormModel::AudioBitrateLimitField = "audio-bitrate-limit"; -const Wt::WFormModel::Field UserFormModel::VideoBitrateLimitField = "video-bitrate-limit"; - - -UserFormView::UserFormView(std::string userId, Wt::WContainerWidget *parent) -: Wt::WTemplateFormView(parent) -{ - - _model = new UserFormModel(userId, this); - - setTemplateText(tr("settings-new-user")); - addFunction("id", &WTemplate::Functions::id); - addFunction("block", &WTemplate::Functions::id); - - // Name - setFormWidget(UserFormModel::NameField, new Wt::WLineEdit()); - - // Email - setFormWidget(UserFormModel::EmailField, new Wt::WLineEdit()); - - // Password - Wt::WLineEdit* passwordEdit = new Wt::WLineEdit(); - setFormWidget(UserFormModel::PasswordField, passwordEdit ); - passwordEdit->setEchoMode(Wt::WLineEdit::Password); - - // Password confirmation - Wt::WLineEdit* passwordConfirmEdit = new Wt::WLineEdit(); - setFormWidget(UserFormModel::PasswordConfirmField, passwordConfirmEdit); - passwordConfirmEdit->setEchoMode(Wt::WLineEdit::Password); - - bindString("access", "Access"); - - // Admin Field - setFormWidget(UserFormModel::AdminField, new Wt::WCheckBox()); - - // AudioBitrate - Wt::WComboBox *audioBitrateCB = new Wt::WComboBox(); - setFormWidget(UserFormModel::AudioBitrateLimitField, audioBitrateCB); - audioBitrateCB->setStyleClass("span2"); - audioBitrateCB->setModel(_model->audioBitrateModel()); - - // VideoBitrate - Wt::WComboBox *videoBitrateCB = new Wt::WComboBox(); - setFormWidget(UserFormModel::VideoBitrateLimitField, videoBitrateCB); - videoBitrateCB->setStyleClass("span2"); - videoBitrateCB->setModel(_model->videoBitrateModel()); - - // Title & Buttons - Wt::WString title; - if (userId.empty()) { - title = Wt::WString("Create user"); - } - else { - Wt::Dbo::Transaction transaction (DboSession()); - Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId( userId ); - - Wt::WString userName; - if (authUser.isValid()) - userName = authUser.identity(Wt::Auth::Identity::LoginName); - else - ; // TODO display user deleted and close the widget - - title = Wt::WString("Edit user {1}").arg(userName); - } - - bindString("title", title); - - Wt::WPushButton *saveButton = new Wt::WPushButton(); - if (userId.empty()) { - saveButton->setText("Create user"); - saveButton->setStyleClass("btn-success"); - } - else - { - saveButton->setText("Save"); - saveButton->setStyleClass("btn-primary"); - } - bindWidget("save-button", saveButton); - saveButton->clicked().connect(this, &UserFormView::processSave); - - Wt::WPushButton *cancelButton = new Wt::WPushButton("Cancel"); - bindWidget("cancel-button", cancelButton); - cancelButton->clicked().connect(this, &UserFormView::processCancel); - - updateView(_model); - -} - - void -UserFormView::processCancel() -{ - // parent widget will delete this widget - completed().emit(false); -} - -void -UserFormView::processSave() -{ - updateModel(_model); - - - if (_model->validate()) - { - // commit model into DB - if (_model->saveData() ) - { - // parent widget will delete this widget - completed().emit(true); - } - // else TODO display a nice error message - } - else - { - updateView(_model); - } -} - -} // namespace Settings -} // namespace UserInterface diff --git a/src/ui/settings/SettingsUserFormView.hpp b/src/ui/settings/SettingsUserFormView.hpp deleted file mode 100644 index 1e495784..00000000 --- a/src/ui/settings/SettingsUserFormView.hpp +++ /dev/null @@ -1,55 +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 UI_SETTINGS_USER_FORM_VIEW_HPP -#define UI_SETTINGS_USER_FORM_VIEW_HPP - -#include -#include -#include - -namespace UserInterface { -namespace Settings { - -class UserFormModel; - -class UserFormView : public Wt::WTemplateFormView -{ - public: - - UserFormView(std::string userId, Wt::WContainerWidget *parent = 0); - - // Signal emitted once the form is completed - Wt::Signal& completed() { return _sigCompleted; } - - private: - - Wt::Signal _sigCompleted; - - void processSave(); - void processCancel(); - - UserFormModel* _model; - -}; - -} // namespace Settings -} // namespace UserInterface - -#endif diff --git a/src/ui/settings/SettingsUsers.cpp b/src/ui/settings/SettingsUsers.cpp deleted file mode 100644 index 314a2a59..00000000 --- a/src/ui/settings/SettingsUsers.cpp +++ /dev/null @@ -1,202 +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 -#include -#include - -#include - -#include "utils/Logger.hpp" - -#include "SettingsUserFormView.hpp" -#include "LmsApplication.hpp" - -#include "SettingsUsers.hpp" - -namespace UserInterface { -namespace Settings { - -Users::Users(Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent) -{ - // Stack two widgets: - _stack = new Wt::WStackedWidget(this); - - // 1/ the user table view - { - Wt::WGroupBox *container = new Wt::WGroupBox("Users", _stack); - - _table = new Wt::WTable(container); - - _table->addStyleClass("table form-inline"); - - _table->toggleStyleClass("table-hover", true); - _table->toggleStyleClass("table-striped", true); - - _table->setHeaderCount(1); - - _table->elementAt(0, 0)->addWidget(new Wt::WText("#")); - _table->elementAt(0, 1)->addWidget(new Wt::WText("Name")); - _table->elementAt(0, 2)->addWidget(new Wt::WText("e-Mail")); - _table->elementAt(0, 3)->addWidget(new Wt::WText("Admin")); - - Wt::WPushButton* addBtn = new Wt::WPushButton("Add User"); - addBtn->setStyleClass("btn-success"); - container->addWidget( addBtn ); - addBtn->clicked().connect(boost::bind(&Users::handleCreateUser, this, "")); - } - - refresh(); -} - -void -Users::refresh(void) -{ - - assert(_table->rowCount() > 0); - for (int i = _table->rowCount() - 1; i > 0; --i) - _table->deleteRow(i); - - Wt::Dbo::Transaction transaction(DboSession()); - - const Wt::Auth::User& currentUser = CurrentAuthUser(); - - std::vector users = Database::User::getAll(DboSession()); - - std::size_t userIndex = 1; - for (std::size_t i = 0; i < users.size(); ++i) - { - - std::string userId = Database::User::getId(users[i]); - - Wt::Auth::User authUser; - - // Hack try/catch here since it may fail! - try { - authUser = DbHandler().getUserDatabase().findWithId( userId ); - } - catch(Wt::Dbo::Exception& e) - { - LMS_LOG(UI, ERROR) << "Caught exception when getting userId=" << userId << ": " << e.code(); - continue; - } - - if (!authUser.isValid()) { - LMS_LOG(UI, ERROR) << "Users::refresh: skipping invalid userId = " << userId; - continue; - } - - _table->elementAt(userIndex, 0)->addWidget(new Wt::WText( Wt::WString::fromUTF8("{1}").arg(userIndex))); - _table->elementAt(userIndex, 1)->addWidget(new Wt::WText( authUser.identity(Wt::Auth::Identity::LoginName)) ); - - Wt::WText* email = new Wt::WText(); - if (!authUser.email().empty()) { - email->setText(authUser.email()); - } - else { - email->setStyleClass("alert-danger"); - email->setText(authUser.unverifiedEmail()); - } - _table->elementAt(userIndex, 2)->addWidget( email ) ; - _table->elementAt(userIndex, 3)->addWidget(new Wt::WText( users[i]->isAdmin() ? "Yes" : "No" )); - - Wt::WPushButton* editBtn = new Wt::WPushButton("Edit"); - _table->elementAt(userIndex, 4)->addWidget(editBtn); - editBtn->clicked().connect(boost::bind( &Users::handleCreateUser, this, userId)); - - if (currentUser != authUser) - { - Wt::WPushButton* delBtn = new Wt::WPushButton("Delete"); - delBtn->setStyleClass("btn-danger"); - delBtn->setMargin(5, Wt::Left); - _table->elementAt(userIndex, 4)->addWidget(delBtn); - delBtn->clicked().connect(boost::bind( &Users::handleDelUser, this, authUser.identity(Wt::Auth::Identity::LoginName), userId)); - } - - ++userIndex; - } - -} - -void -Users::handleDelUser(Wt::WString loginNameIdentity, std::string id) -{ - Wt::WMessageBox *messageBox = new Wt::WMessageBox - ("Delete User", - Wt::WString( "Deleting user '{1}'?").arg(loginNameIdentity), - Wt::Question, Wt::Yes | Wt::No); - - messageBox->setModal(true); - - messageBox->buttonClicked().connect(std::bind([=] () { - if (messageBox->buttonResult() == Wt::Yes) - { - Wt::Dbo::Transaction transaction(DboSession()); - - // Delete the user - Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId( id ); - - DbHandler().getUserDatabase().deleteUser( authUser ); - - Database::User::pointer user = Database::User::getById(DboSession(), id); - if (user) - user.remove(); - - refresh(); - } - - delete messageBox; - })); - - messageBox->show(); - -} - -void -Users::handleCreateUser(std::string id) -{ - assert(_stack->count() == 1); - - UserFormView* userFormView = new UserFormView(id, _stack); - userFormView->completed().connect(this, &Users::handleUserFormCompleted); - - _stack->setCurrentIndex(1); -} - -void -Users::handleUserFormCompleted(bool changed) -{ - _stack->setCurrentIndex(0); - - // Refresh the user table if a change has been made - if (changed) - refresh(); - - // Delete the form view - delete _stack->widget(1); - -} - -} // namespace UserInterface -} // namespace Settings - - diff --git a/src/ui/settings/SettingsUsers.hpp b/src/ui/settings/SettingsUsers.hpp deleted file mode 100644 index 4f67f672..00000000 --- a/src/ui/settings/SettingsUsers.hpp +++ /dev/null @@ -1,52 +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 UI_SETTINGS_USERS_HPP -#define UI_SETTINGS_USERS_HPP - -#include -#include -#include - -namespace UserInterface { -namespace Settings { - -class Users : public Wt::WContainerWidget -{ - public: - Users(Wt::WContainerWidget *parent = 0); - - void refresh(); - - private: - - void handleUserFormCompleted(bool changed); - - void handleDelUser(Wt::WString loginNameIdentity, std::string id); - void handleCreateUser(std::string id); // set the id in order to edit the user - - Wt::WStackedWidget* _stack; - Wt::WTable* _table; -}; - -} // namespace UserInterface -} // namespace Settings - -#endif - diff --git a/src/ui/video/VideoDatabaseWidget.cpp b/src/ui/video/VideoDatabaseWidget.cpp deleted file mode 100644 index 5e25e8bc..00000000 --- a/src/ui/video/VideoDatabaseWidget.cpp +++ /dev/null @@ -1,143 +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 -#include - -#include -#include - -#include "LmsApplication.hpp" - -#include "VideoDatabaseWidget.hpp" - -namespace UserInterface { - -VideoDatabaseWidget::VideoDatabaseWidget(Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent) -{ - _table = new Wt::WTable( this ); - _table->setHeaderCount(1); - - _table->addStyleClass("table form-inline"); - - _table->toggleStyleClass("table-hover", true); - _table->toggleStyleClass("table-striped", true); - - updateView( boost::filesystem::path(), 0); -} - -void -VideoDatabaseWidget::addHeader(void) -{ - _table->elementAt(0, 0)->addWidget(new Wt::WText("Name")); - _table->elementAt(0, 1)->addWidget(new Wt::WText("Duration")); - _table->elementAt(0, 2)->addWidget(new Wt::WText("Action")); -} - -void -VideoDatabaseWidget::addDirectory(const std::string& name, boost::filesystem::path path, size_t depth) -{ - - int row = _table->rowCount(); - - new Wt::WText(Wt::WString::fromUTF8( name ), _table->elementAt(row, 0)); - new Wt::WText(Wt::WString::fromUTF8( " " ), _table->elementAt(row, 1)); - - Wt::WPushButton* btn = new Wt::WPushButton(Wt::WString::fromUTF8( "Open"), _table->elementAt(row, 2)); - - btn->clicked().connect(std::bind([=] () { - updateView(path, depth); - })); - -} - - -void -VideoDatabaseWidget::addVideo(const std::string& name, const boost::posix_time::time_duration& duration, const boost::filesystem::path& path) -{ - - int row = _table->rowCount(); - - new Wt::WText(Wt::WString::fromUTF8( name ), _table->elementAt(row, 0)); - new Wt::WText(Wt::WString::fromUTF8( boost::posix_time::to_simple_string( duration )), _table->elementAt(row, 1)); - - Wt::WPushButton* btn = new Wt::WPushButton(Wt::WString::fromUTF8( "Play"), _table->elementAt(row, 2)); - - btn->clicked().connect(std::bind([=] () { - playVideo().emit(path); - })); -} - - -void -VideoDatabaseWidget::updateView(boost::filesystem::path directory, size_t depth) -{ - _table->clear(); - addHeader(); - - // If directory is not valid, add the root Media Directories - if (depth == 0) - { - Wt::Dbo::Transaction transaction ( DboSession() ); - - std::vector dirs - = Database::MediaDirectory::getByType(DboSession(), Database::MediaDirectory::Video); - - BOOST_FOREACH(Database::MediaDirectory::pointer dir, dirs) - { - addDirectory( dir->getPath().filename().string(), - dir->getPath(), - depth + 1); - } - } - else - { - addDirectory( "..", directory.parent_path(), depth - 1); - - // Iterators over files in the directory - // If directory, add a directory entry - // If video, add a video entry - std::vector paths; - std::copy(boost::filesystem::directory_iterator(directory), boost::filesystem::directory_iterator(), std::back_inserter(paths)); - - std::sort(paths.begin(), paths.end()); - - BOOST_FOREACH(const boost::filesystem::path& path, paths) - { - if (boost::filesystem::is_directory(path)) - addDirectory( path.filename().string(), path, depth + 1); - else if (boost::filesystem::is_regular(path) ) - { - Wt::Dbo::Transaction transaction ( DboSession() ); - - Database::Video::pointer video = Database::Video::getByPath( DboSession(), path); - if (video) - addVideo( video->getName(), video->getDuration(), path); - } - } - } - -} - - -} // namespace UserInterface - diff --git a/src/ui/video/VideoDatabaseWidget.hpp b/src/ui/video/VideoDatabaseWidget.hpp deleted file mode 100644 index 8ab399d4..00000000 --- a/src/ui/video/VideoDatabaseWidget.hpp +++ /dev/null @@ -1,52 +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 VIDEO_DB_WIDGET_HPP -#define VIDEO_DB_WIDGET_HPP - -#include -#include - -namespace UserInterface { - -class VideoDatabaseWidget : public Wt::WContainerWidget -{ - public: - VideoDatabaseWidget(Wt::WContainerWidget *parent = 0); - - // Signals - Wt::Signal< boost::filesystem::path >& playVideo() { return _playVideo; } - - private: - - void addHeader(void); - void addDirectory(const std::string& name, boost::filesystem::path path, size_t depth); - void addVideo(const std::string& name, const boost::posix_time::time_duration& duration, const boost::filesystem::path& path); - - void updateView(boost::filesystem::path directory, size_t depth); - - Wt::Signal< boost::filesystem::path > _playVideo; - - Wt::WTable* _table; -}; - -} // namespace UserInterface - -#endif - diff --git a/src/ui/video/VideoMediaPlayerWidget.cpp b/src/ui/video/VideoMediaPlayerWidget.cpp deleted file mode 100644 index 2062cd4d..00000000 --- a/src/ui/video/VideoMediaPlayerWidget.cpp +++ /dev/null @@ -1,200 +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 all types plus i/o - -#include - -#include "VideoParametersDialog.hpp" - -#include "VideoMediaPlayerWidget.hpp" - -namespace UserInterface { - -Wt::WMediaPlayer::Encoding -AvEncoding_to_WtEncoding(Av::Encoding encoding) -{ - switch( encoding ) - { - case Av::Encoding::OGA: return Wt::WMediaPlayer::OGA; - case Av::Encoding::OGV: return Wt::WMediaPlayer::OGV; - case Av::Encoding::MP3: return Wt::WMediaPlayer::MP3; - case Av::Encoding::WEBMA: return Wt::WMediaPlayer::WEBMA; - case Av::Encoding::WEBMV: return Wt::WMediaPlayer::WEBMV; - case Av::Encoding::FLA: return Wt::WMediaPlayer::FLA; - case Av::Encoding::FLV: return Wt::WMediaPlayer::FLV; - case Av::Encoding::M4A: return Wt::WMediaPlayer::M4A; - case Av::Encoding::M4V: return Wt::WMediaPlayer::M4V; - } - assert(0); -} - - -VideoMediaPlayerWidget::VideoMediaPlayerWidget( const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters, Wt::WContainerWidget *parent) - : Wt::WContainerWidget(parent), - _mediaResource(nullptr), - _currentParameters(parameters), - _dialog(nullptr) -{ - _mediaPlayer = new Wt::WMediaPlayer( Wt::WMediaPlayer::Video, this ); -// _mediaPlayer->setAlternativeContent (new Wt::WText("You don't have HTML5 audio support!")); -// _mediaPlayer->setOptions( Wt::WMediaPlayer::Autoplay ); -// _mediaPlayer->addSource( Wt::WMediaPlayer::WEBMV, "" ); - - { - Wt::WContainerWidget *container = new Wt::WContainerWidget(this); - - _playBtn = new Wt::WPushButton("Play", container ); - _pauseBtn = new Wt::WPushButton("Pause", container ); - - _curTime = new Wt::WText(container); - _timeSlider = new Wt::WSlider( container ); - _duration = new Wt::WText(container); - - _volumeSlider = new Wt::WSlider( container ); - _volumeSlider->setRange(0,100); - _volumeSlider->setValue(_mediaPlayer->volume() * 100); - - _mediaPlayer->setControlsWidget( container ); - _mediaPlayer->setButton(Wt::WMediaPlayer::Play, _playBtn); - _mediaPlayer->setButton(Wt::WMediaPlayer::Pause, _pauseBtn); - - _mediaPlayer->setText( Wt::WMediaPlayer::CurrentTime, _curTime); - _mediaPlayer->setText( Wt::WMediaPlayer::Duration, _duration); - - _mediaPlayer->timeUpdated().connect(this, &VideoMediaPlayerWidget::handleTimeUpdated); - - Wt::WPushButton* fullScreenButton = new Wt::WPushButton("Fullscreen", container); - _mediaPlayer->setButton(Wt::WMediaPlayer::FullScreen, fullScreenButton); - - Wt::WPushButton* restoreScreenButton = new Wt::WPushButton("Restore screen", container); - _mediaPlayer->setButton(Wt::WMediaPlayer::RestoreScreen, restoreScreenButton); - - _mediaPlayer->timeUpdated().connect(this, &VideoMediaPlayerWidget::handleTimeUpdated); - - _timeSlider->valueChanged().connect(this, &VideoMediaPlayerWidget::handlePlayOffset); - _timeSlider->sliderMoved().connect(this, &VideoMediaPlayerWidget::handleSliderMoved); - - _volumeSlider->sliderMoved().connect(this, &VideoMediaPlayerWidget::handleVolumeSliderMoved); - } - - - Wt::WPushButton* closeButton = new Wt::WPushButton("Close", this); - closeButton->clicked().connect( this, &VideoMediaPlayerWidget::handleClose ); - - Wt::WPushButton* parametersButton = new Wt::WPushButton("Parameters", this); - parametersButton->clicked().connect( this, &VideoMediaPlayerWidget::handleParametersEdit ); - - _currentFile = mediaFile.getPath(); - - load(parameters); - - _timeSlider->setRange(0, mediaFile.getDuration().total_seconds() ); - _duration->setText( boost::posix_time::to_simple_string( mediaFile.getDuration() )); -} - -void -VideoMediaPlayerWidget::load(Av::TranscodeParameters parameters) -{ - _mediaPlayer->clearSources(); - - _currentParameters = parameters; - - _mediaInternalLink.setResource( nullptr ); - if (_mediaResource) - delete _mediaResource; - - _mediaResource = new AvConvTranscodeStreamResource( _currentFile, parameters, this ); - _mediaInternalLink.setResource( _mediaResource ); - - _mediaPlayer->addSource( AvEncoding_to_WtEncoding(parameters.getEncoding()), _mediaInternalLink ); - - _timeSlider->setValue( 0 ); - - - _mediaPlayer->play(); -} - -void -VideoMediaPlayerWidget::handlePlayOffset(int offsetSecs) -{ - _currentParameters.setOffset( boost::posix_time::seconds(offsetSecs) ); - load( _currentParameters ); - - _timeSlider->setValue( offsetSecs ); -} - -void -VideoMediaPlayerWidget::handleSliderMoved(int value) -{ - _curTime->setText( boost::posix_time::to_simple_string( boost::posix_time::seconds( value ) ) ); -} - -void -VideoMediaPlayerWidget::handleTimeUpdated(void) -{ - - boost::posix_time::time_duration currentTime ( boost::posix_time::seconds( _mediaPlayer->currentTime() + _currentParameters.getOffset().total_seconds())); - - _timeSlider->setValue( currentTime.total_seconds() ); - _curTime->setText( boost::posix_time::to_simple_string( currentTime) ); -} - -void -VideoMediaPlayerWidget::handleVolumeSliderMoved(int value) -{ - _mediaPlayer->setVolume( value / 100. ); -} - -void -VideoMediaPlayerWidget::handleClose(void) -{ - _close.emit(); -} - -void -VideoMediaPlayerWidget::handleParametersEdit(void) -{ -/* - _dialog = new VideoParametersDialog("Parameters"); - _dialog->load(_currentParameters); - - _dialog->show(); - - _dialog->finished().connect(this, &VideoMediaPlayerWidget::handleParametersDone); - */ -} - -void -VideoMediaPlayerWidget::handleParametersDone(Wt::WDialog::DialogCode code) -{ - assert(_dialog != nullptr); - - if (code == Wt::WDialog::Accepted) - { - _dialog->save( _currentParameters ); - - // TODO SYNC current offset with player? - // HACK use slider current value - handlePlayOffset( _timeSlider->value()); - } - -} - -} // namespace UserInterface diff --git a/src/ui/video/VideoMediaPlayerWidget.hpp b/src/ui/video/VideoMediaPlayerWidget.hpp deleted file mode 100644 index ef292c52..00000000 --- a/src/ui/video/VideoMediaPlayerWidget.hpp +++ /dev/null @@ -1,85 +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 __VIDEO_MEDIA_PLAYER_WIDGET_HPP -#define __VIDEO_MEDIA_PLAYER_WIDGET_HPP - -#include -#include -#include -#include -#include - -#include "VideoParametersDialog.hpp" - -#include "av/AvTranscoder.hpp" -#include "resource/AvConvTranscodeStreamResource.hpp" - -namespace UserInterface { - -class VideoMediaPlayerWidget : public Wt::WContainerWidget -{ - public: - - VideoMediaPlayerWidget( const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters, Wt::WContainerWidget *parent = 0); - - Wt::Signal& close() { return _close; }; - - private: - - // Signals - Wt::Signal _close; - - void load(Av::TranscodeParameters parameters); - - // Player controls - void handlePlayOffset(int offsetSecs); - void handleTimeUpdated(void); - void handleSliderMoved(int value); - void handleFullscreen(void); - void handleVolumeSliderMoved(int value); - - void handleClose(void); - - void handleParametersEdit(void); - void handleParametersDone(Wt::WDialog::DialogCode); - - boost::filesystem::path _currentFile; - - // Core - Wt::WMediaPlayer* _mediaPlayer; - AvConvTranscodeStreamResource* _mediaResource; - Wt::WLink _mediaInternalLink; - - // Controls - Av::TranscodeParameters _currentParameters; - Wt::WPushButton* _playBtn; - Wt::WPushButton* _pauseBtn; - Wt::WSlider* _timeSlider; - Wt::WSlider* _volumeSlider; - Wt::WText* _curTime; - Wt::WText* _duration; - - VideoParametersDialog* _dialog; -}; - -} // namespace UserInterface - -#endif - diff --git a/src/ui/video/VideoParametersDialog.cpp b/src/ui/video/VideoParametersDialog.cpp deleted file mode 100644 index 88b578c2..00000000 --- a/src/ui/video/VideoParametersDialog.cpp +++ /dev/null @@ -1,143 +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 -#include - -#include "VideoParametersDialog.hpp" - -namespace UserInterface { - -static const std::list streamTypes = {Av::Stream::Type::Video, Av::Stream::Type::Audio, Av::Stream::Type::Subtitle}; - -VideoParametersDialog::VideoParametersDialog(Wt::WString windowTitle, Wt::WDialog* parent) -: Wt::WDialog(windowTitle, parent) -{ - - Wt::WTable* layout = new Wt::WTable(contents()); - - createStreamWidgets("Video", Av::Stream::Type::Video, layout); - createStreamWidgets("Audio", Av::Stream::Type::Audio, layout); - createStreamWidgets("Subtitles", Av::Stream::Type::Subtitle, layout); - - Wt::WPushButton *ok = new Wt::WPushButton("Apply", contents()); - ok->clicked().connect(this, &Wt::WDialog::accept); - - Wt::WPushButton *cancel = new Wt::WPushButton("Cancel", contents()); - cancel->clicked().connect(this, &Wt::WDialog::reject); -} - -void -VideoParametersDialog::createStreamWidgets(const Wt::WString& labelString, Av::Stream::Type type, Wt::WTable* layout) -{ - int row = layout->rowCount(); - - Wt::WLabel* label = new Wt::WLabel(labelString); - Wt::WComboBox *combo = new Wt::WComboBox(); - label->setBuddy(combo); - - layout->elementAt(row, 0)->addWidget(label); - layout->elementAt(row, 1)->addWidget(combo); - - Wt::WStringListModel* model = new Wt::WStringListModel(combo); - - combo->setModel(model); - - _streamSelection.insert( std::make_pair(type, std::make_pair(combo, model) ) ); -} - - -void -VideoParametersDialog::handleApply() -{ - _apply.emit(); -} - -void -VideoParametersDialog::addStreams(Wt::WStringListModel* model, const std::vector& streams) -{ - for (const Av::Stream& stream : streams) - { - model->addString(stream.desc); - model->setData(model->rowCount(), 0, stream.id, Wt::UserRole); - } -} - -void -VideoParametersDialog::selectStream(const Wt::WStringListModel* model, int streamId, Wt::WComboBox* combo) -{ - for (int idStream = 0; idStream < model->rowCount(); ++idStream) - { - int id = boost::any_cast( model->data( model->index(idStream, 0), Wt::UserRole)); - if (id == streamId) - { - combo->setCurrentIndex(idStream); - break; - } - } -} - - -void -VideoParametersDialog::load(const Av::MediaFile& mediaFile, Av::TranscodeParameters currentParameters) -{ - // Populate the combox with the available streams - // And then show the selected one - for (Av::Stream::Type streamType : streamTypes) - { - Wt::WComboBox* combo = _streamSelection[streamType].first; - Wt::WStringListModel* model = _streamSelection[streamType].second; - - addStreams(model, mediaFile.getStreams(streamType) ); - - // Pre Select if necessary - std::set selectedStreams = currentParameters.getSelectedStreamIds(); - for (Av::Stream stream : mediaFile.getStreams(streamType)) - { - if (selectedStreams.find(stream.id) != selectedStreams.end()) - { - selectStream(model, stream.id, combo); - break; - } - } - } -} - -void -VideoParametersDialog::save(Av::TranscodeParameters& parameters) -{ - // Get stream selected, if any - for (Av::Stream::Type streamType : streamTypes) - { - Wt::WComboBox* combo = _streamSelection[streamType].first; - Wt::WStringListModel* model = _streamSelection[streamType].second; - - if (combo->currentIndex() >= 0) - { - int streamId = boost::any_cast( model->data(model->index(combo->currentIndex(), 0), Wt::UserRole)); - - parameters.addStream(streamId); - } - - } -} - -} // namespace UserInterface - diff --git a/src/ui/video/VideoParametersDialog.hpp b/src/ui/video/VideoParametersDialog.hpp deleted file mode 100644 index 01219c01..00000000 --- a/src/ui/video/VideoParametersDialog.hpp +++ /dev/null @@ -1,64 +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 . - */ - -#pragma once - -#include - -#include -#include -#include -#include -#include - -#include "av/AvTranscoder.hpp" - -namespace UserInterface { - -class VideoParametersDialog : public Wt::WDialog -{ - public: - // parameters to be edited - VideoParametersDialog(Wt::WString windowTitle, Wt::WDialog* parent = 0); - - // Populates widget contents using these paramaters - void load(const Av::MediaFile& mediaFile, Av::TranscodeParameters currentParameters); - - // Save widgets contents into these parameters - void save(Av::TranscodeParameters& parameters); - - // Signal to be emitted if parameters are changed - Wt::Signal& apply() { return _apply; } - - private: - - void handleApply(void); - - // Stream handling - void createStreamWidgets(const Wt::WString& label, Av::Stream::Type type, Wt::WTable* layout); - void addStreams(Wt::WStringListModel* model, const std::vector& streams); - void selectStream(const Wt::WStringListModel* model, int streamId, Wt::WComboBox* combo); - - Wt::Signal _apply; - - std::map > _streamSelection; -}; - -} // namespace UserInterface - diff --git a/src/ui/video/VideoWidget.cpp b/src/ui/video/VideoWidget.cpp deleted file mode 100644 index bfa38d7f..00000000 --- a/src/ui/video/VideoWidget.cpp +++ /dev/null @@ -1,91 +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 - -#include "logger/Logger.hpp" - -#include "LmsApplication.hpp" - -#include "VideoWidget.hpp" - -namespace UserInterface { - -VideoWidget::VideoWidget(Wt::WContainerWidget* parent ) -: Wt::WContainerWidget(parent) -{ - - _videoDbWidget = new VideoDatabaseWidget(this); - - _videoDbWidget->playVideo().connect(this, &VideoWidget::playVideo); - - -} - - -void -VideoWidget::search(const std::string& searchText) -{ - //TODO -} - -void -VideoWidget::playVideo(boost::filesystem::path p) -{ - LMS_LOG(UI, DEBUG) << "Want to play video " << p << "'"; - - std::size_t audioBitrate = 0; - std::size_t videoBitrate = 0; - - // Get user preferences - { - Wt::Dbo::Transaction transaction(DboSession()); - - audioBitrate = CurrentUser()->getMaxAudioBitrate(); - videoBitrate = CurrentUser()->getMaxVideoBitrate(); - } - - LMS_LOG(UI, DEBUG) << "Max bitrate set to " << videoBitrate << "/" << audioBitrate; - - Av::MediaFile mediaFile(p); - - if (!mediaFile.open()) - return; - - if (!mediaFile.scan()) - return; - - Av::TranscodeParameters parameters; - - parameters.setEncoding( Av::Encoding::WEBMV ); - - VideoMediaPlayerWidget *mediaPlayer = new VideoMediaPlayerWidget(mediaFile, parameters, this); - - mediaPlayer->close().connect(std::bind([=] () - { - _videoDbWidget->setHidden(false); - delete mediaPlayer; - })); - - _videoDbWidget->setHidden(true); -} - -} // namespace UserInterface - diff --git a/src/ui/video/VideoWidget.hpp b/src/ui/video/VideoWidget.hpp deleted file mode 100644 index 68b2109a..00000000 --- a/src/ui/video/VideoWidget.hpp +++ /dev/null @@ -1,53 +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 VIDEO_WIDGET_HPP -#define VIDEO_WIDGET_HPP - -#include - -#include - -#include "video/VideoMediaPlayerWidget.hpp" -#include "video/VideoDatabaseWidget.hpp" - -namespace UserInterface { - -class VideoWidget : public Wt::WContainerWidget -{ - public: - - VideoWidget(Wt::WContainerWidget* parent = 0); - - void search(const std::string& searchText); - - private: - - void backToList(void); - void playVideo(boost::filesystem::path p); - - VideoDatabaseWidget* _videoDbWidget; - VideoMediaPlayerWidget* _mediaPlayer; - -}; - -} // namespace UserInterface - -#endif -