From 8d17bf37856d0108eff86139ebe20d294421127a Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 23 Dec 2014 16:55:57 +0100 Subject: [PATCH] [UI] Added column 'releases' for artist filter. Added column 'date' for release filter --- src/database-updater/DatabaseUpdater.cpp | 8 +- src/database/AudioTypes.hpp | 17 +- src/database/Track.cpp | 85 +++++----- src/ui/audio/Audio.cpp | 6 +- src/ui/audio/TableFilter.cpp | 205 ++++++++++++++++++----- src/ui/audio/TableFilter.hpp | 65 ++++++- 6 files changed, 284 insertions(+), 102 deletions(-) diff --git a/src/database-updater/DatabaseUpdater.cpp b/src/database-updater/DatabaseUpdater.cpp index 37c1c196..5543ed32 100644 --- a/src/database-updater/DatabaseUpdater.cpp +++ b/src/database-updater/DatabaseUpdater.cpp @@ -296,9 +296,9 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) return; } if (items.find(MetaData::Type::Duration) == items.end() - || boost::any_cast(items[MetaData::Type::Duration]).total_seconds() == 0) + || boost::any_cast(items[MetaData::Type::Duration]).total_seconds() <= 0) { - LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Skipped '" << file << "' (no duration or duration 0)"; + LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Skipped '" << file << "' (no duration or duration <= 0)"; // If Track exists here, delete it! if (track) { @@ -361,6 +361,7 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) track.modify()->setLastWriteTime(lastWriteTime); track.modify()->setName(title); + track.modify()->setDuration( boost::any_cast(items[MetaData::Type::Duration]) ); { std::string trackGenreList; @@ -387,9 +388,6 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) if (items.find(MetaData::Type::DiscNumber) != items.end()) track.modify()->setDiscNumber( boost::any_cast(items[MetaData::Type::DiscNumber]) ); - if (items.find(MetaData::Type::Duration) != items.end()) - track.modify()->setDuration( boost::any_cast(items[MetaData::Type::Duration]) ); - if (items.find(MetaData::Type::Date) != items.end()) track.modify()->setDate( boost::any_cast(items[MetaData::Type::Date]) ); diff --git a/src/database/AudioTypes.hpp b/src/database/AudioTypes.hpp index eeacb22c..e0124788 100644 --- a/src/database/AudioTypes.hpp +++ b/src/database/AudioTypes.hpp @@ -67,8 +67,9 @@ class Genre static pointer getByName(Wt::Dbo::Session& session, const std::string& name); static pointer getNone(Wt::Dbo::Session& session); static Wt::Dbo::collection getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1); - static Wt::Dbo::Query > getAllQuery(Wt::Dbo::Session& session, SearchFilter& filter); - static void updateGenreQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel >& model, SearchFilter filter, const std::vector& columnNames = std::vector()); + typedef boost::tuple GenreResult; + static Wt::Dbo::Query getAllQuery(Wt::Dbo::Session& session, SearchFilter& filter); + static void updateGenreQueryModel(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, const std::string& name); @@ -115,8 +116,12 @@ class Track // Utility fonctions // MVC models for the user interface static void updateTracksQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< pointer >& model, SearchFilter filter, const std::vector& columnNames = std::vector()); - static void updateReleaseQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel >& model, SearchFilter filter, const std::vector& columnNames = std::vector()); - static void updateArtistQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel >& model, SearchFilter filter, const std::vector& columnNames = std::vector()); + // Release name, year, track counts + typedef boost::tuple ReleaseResult; + static void updateReleaseQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames = std::vector()); + // Artist name, albums, tracks + typedef boost::tuple ArtistResult; + static void updateArtistQueryModel(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, const boost::filesystem::path& p); @@ -174,8 +179,8 @@ class Track private: static Wt::Dbo::Query< pointer > getAllQuery(Wt::Dbo::Session& session, SearchFilter filter); - static Wt::Dbo::Query > getReleasesQuery(Wt::Dbo::Session& session, SearchFilter filter); - static Wt::Dbo::Query > getArtistsQuery(Wt::Dbo::Session& session, SearchFilter filter); + static Wt::Dbo::Query getReleasesQuery(Wt::Dbo::Session& session, SearchFilter filter); + static Wt::Dbo::Query getArtistsQuery(Wt::Dbo::Session& session, SearchFilter filter); static const std::size_t _maxNameLength = 128; diff --git a/src/database/Track.cpp b/src/database/Track.cpp index 3312d5c4..dc5b77aa 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -200,13 +200,13 @@ Track::getAll(Wt::Dbo::Session& session, SearchFilter filter, int offset, int si return getAllQuery(session, filter).limit(size).offset(offset); } -Wt::Dbo::Query< boost::tuple > +Wt::Dbo::Query Track::getReleasesQuery(Wt::Dbo::Session& session, SearchFilter filter) { SqlQuery sqlQuery = generatePartialQuery(filter); - Wt::Dbo::Query > query - = session.query >("SELECT t.release_name, COUNT(DISTINCT t.id) FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).groupBy("t.release_name").orderBy("t.release_name"); + Wt::Dbo::Query query + = session.query("SELECT t.release_name, t.date, COUNT(DISTINCT t.id) FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).groupBy("t.release_name").orderBy("t.release_name"); BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) query.bind(bindArg); @@ -214,13 +214,29 @@ Track::getReleasesQuery(Wt::Dbo::Session& session, SearchFilter filter) return query; } -Wt::Dbo::Query< boost::tuple > +void +Track::updateReleaseQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) +{ + Wt::Dbo::Query query = getReleasesQuery(session, filter); + + model.setQuery(query, columnNames.empty() ? true : false); + + // TODO do something better + if (columnNames.size() == 3) + { + model.addColumn( "t.release_name", columnNames[0]); + model.addColumn( "t.date", columnNames[1]); + model.addColumn( "COUNT(DISTINCT t.id)", columnNames[2] ); + } +} + +Wt::Dbo::Query Track::getArtistsQuery(Wt::Dbo::Session& session, SearchFilter filter) { SqlQuery sqlQuery = generatePartialQuery(filter); - Wt::Dbo::Query > query - = session.query >( "SELECT t.artist_name, COUNT(DISTINCT t.id) FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).groupBy("t.artist_name").orderBy("t.artist_name"); + Wt::Dbo::Query query + = session.query( "SELECT t.artist_name, COUNT(DISTINCT t.release_name), COUNT(DISTINCT t.id) FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).groupBy("t.artist_name").orderBy("t.artist_name"); BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) query.bind(bindArg); @@ -228,6 +244,23 @@ Track::getArtistsQuery(Wt::Dbo::Session& session, SearchFilter filter) return query; } +void +Track::updateArtistQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) +{ + Wt::Dbo::Query query = getArtistsQuery(session, filter); + + model.setQuery(query, columnNames.empty() ? true : false); + + // TODO do something better + if (columnNames.size() == 3) + { + model.addColumn( "t.artist_name", columnNames.at(0)); + model.addColumn( "COUNT(DISTINCT t.release_name)", columnNames.at(1) ); + model.addColumn( "COUNT(DISTINCT t.id)", columnNames.at(2) ); + } +} + + void Track::updateTracksQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< pointer >& model, SearchFilter filter, const std::vector& columnNames) { @@ -250,36 +283,6 @@ Track::updateTracksQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< po } -void -Track::updateReleaseQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel >& model, SearchFilter filter, const std::vector& columnNames) -{ - Wt::Dbo::Query< boost::tuple > query = getReleasesQuery(session, filter); - - model.setQuery(query, columnNames.empty() ? true : false); - - // TODO do something better - if (columnNames.size() == 2) - { - model.addColumn( "t.release_name", columnNames[0]); - model.addColumn( "COUNT(DISTINCT t.id)" ); - } -} - -void -Track::updateArtistQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel >& model, SearchFilter filter, const std::vector& columnNames) -{ - Wt::Dbo::Query< boost::tuple > query = getArtistsQuery(session, filter); - - model.setQuery(query, columnNames.empty() ? true : false); - - // TODO do something better - if (columnNames.size() == 2) - { - model.addColumn( "t.artist_name", columnNames[0]); - model.addColumn( "COUNT(DISTINCT t.id)" ); - } -} - std::vector Track::getArtists(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) { @@ -366,13 +369,13 @@ Genre::getAll(Wt::Dbo::Session& session, int offset, int size) return session.find().offset(offset).limit(size).orderBy("name"); } -Wt::Dbo::Query > +Wt::Dbo::Query Genre::getAllQuery(Wt::Dbo::Session& session, SearchFilter& filter) { SqlQuery sqlQuery = generatePartialQuery(filter, true); - Wt::Dbo::Query > query - = session.query >( "SELECT g.name, COUNT(DISTINCT t.id) FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).groupBy("g.name").orderBy("g.name"); + Wt::Dbo::Query query + = session.query( "SELECT g.name, COUNT(DISTINCT t.id) FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).groupBy("g.name").orderBy("g.name"); BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) query.bind(bindArg); @@ -381,9 +384,9 @@ Genre::getAllQuery(Wt::Dbo::Session& session, SearchFilter& filter) } void -Genre::updateGenreQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< boost::tuple >& model, SearchFilter filter, const std::vector& columnNames) +Genre::updateGenreQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) { - Wt::Dbo::Query< boost::tuple > query = getAllQuery(session, filter); + Wt::Dbo::Query query = getAllQuery(session, filter); model.setQuery(query, columnNames.empty() ? true : false); // TODO do something better diff --git a/src/ui/audio/Audio.cpp b/src/ui/audio/Audio.cpp index d1675ff9..c55f9bae 100644 --- a/src/ui/audio/Audio.cpp +++ b/src/ui/audio/Audio.cpp @@ -49,15 +49,15 @@ _playQueue(nullptr) // Filters Wt::WHBoxLayout *filterLayout = new Wt::WHBoxLayout(); - TableFilter *filterGenre = new TableFilter(_db, Database::SearchFilter::Field::Genre, { "Genre", "Tracks"} ); + TableFilterGenre *filterGenre = new TableFilterGenre(_db); filterLayout->addWidget(filterGenre); _filterChain.addFilter(filterGenre); - TableFilter *filterArtist = new TableFilter(_db, Database::SearchFilter::Field::Artist, {"Artist", "Tracks"} ); + TableFilterArtist *filterArtist = new TableFilterArtist(_db); filterLayout->addWidget(filterArtist); _filterChain.addFilter(filterArtist); - TableFilter *filterRelease = new TableFilter(_db, Database::SearchFilter::Field::Release, {"Release", "Tracks"}); + TableFilterRelease *filterRelease = new TableFilterRelease(_db); filterLayout->addWidget(filterRelease); _filterChain.addFilter(filterRelease); diff --git a/src/ui/audio/TableFilter.cpp b/src/ui/audio/TableFilter.cpp index f062ebac..83c83648 100644 --- a/src/ui/audio/TableFilter.cpp +++ b/src/ui/audio/TableFilter.cpp @@ -19,6 +19,8 @@ #include +#include + #include "database/AudioTypes.hpp" #include "logger/Logger.hpp" @@ -29,28 +31,15 @@ namespace UserInterface { using namespace Database; -TableFilter::TableFilter(Database::Handler& db, Database::SearchFilter::Field field, std::vector columnNames, Wt::WContainerWidget* parent) -: Wt::WTableView( parent ), -Filter(), -_db(db), -_field(field) +TableFilterGenre::TableFilterGenre(Database::Handler& db, Wt::WContainerWidget* parent) +: Wt::WTableView( parent ), Filter(), +_db(db) { + const std::vector columnNames = {"Genre", "Tracks"}; + SearchFilter filter; - switch (field) - { - case Database::SearchFilter::Field::Artist: - Track::updateArtistQueryModel(_db.getSession(), _queryModel, filter, columnNames); - break; - case Database::SearchFilter::Field::Release: - Track::updateReleaseQueryModel(_db.getSession(), _queryModel, filter, columnNames); - break; - case Database::SearchFilter::Field::Genre: - Genre::updateGenreQueryModel(_db.getSession(), _queryModel, filter, columnNames); - break; - default: - break; - } + Genre::updateGenreQueryModel(_db.getSession(), _queryModel, filter, columnNames); this->setSelectionMode(Wt::ExtendedSelection); this->setSortingEnabled(true); @@ -59,7 +48,7 @@ _field(field) this->setColumnWidth(1, 50); - this->selectionChanged().connect(this, &TableFilter::emitUpdate); + this->selectionChanged().connect(this, &TableFilterGenre::emitUpdate); setLayoutSizeAware(true); @@ -82,7 +71,7 @@ _field(field) } void -TableFilter::layoutSizeChanged (int width, int height) +TableFilterGenre::layoutSizeChanged (int width, int height) { std::size_t trackColumnSize = this->columnWidth(1).toPixels(); // Set the remaining size for the name column @@ -91,27 +80,14 @@ TableFilter::layoutSizeChanged (int width, int height) // Set constraints on this filter void -TableFilter::refresh(SearchFilter& filter) +TableFilterGenre::refresh(SearchFilter& filter) { - switch (_field) - { - case Database::SearchFilter::Field::Artist: - Track::updateArtistQueryModel(_db.getSession(), _queryModel, filter); - break; - case Database::SearchFilter::Field::Release: - Track::updateReleaseQueryModel(_db.getSession(), _queryModel, filter); - break; - case Database::SearchFilter::Field::Genre: - Genre::updateGenreQueryModel(_db.getSession(), _queryModel, filter); - break; - default: - break; - } + Genre::updateGenreQueryModel(_db.getSession(), _queryModel, filter); } // Get constraint created by this filter void -TableFilter::getConstraint(SearchFilter& filter) +TableFilterGenre::getConstraint(SearchFilter& filter) { Wt::WModelIndexSet indexSet = this->selectedIndexes(); @@ -120,11 +96,160 @@ TableFilter::getConstraint(SearchFilter& filter) if (!index.isValid()) continue; - const ResultType& result = _queryModel.resultRow( index.row() ); + std::string name = _queryModel.resultRow( index.row() ).get<0>(); - std::string name = result.get<0>(); + filter.exactMatch[Database::SearchFilter::Field::Genre].push_back(name); + } +} - filter.exactMatch[_field].push_back(name); +TableFilterArtist::TableFilterArtist(Database::Handler& db, Wt::WContainerWidget* parent) +: Wt::WTableView( parent ), Filter(), +_db(db) +{ + const std::vector columnNames = {"Artist", "Releases", "Tracks"}; + + SearchFilter filter; + + Track::updateArtistQueryModel(_db.getSession(), _queryModel, filter, columnNames); + + this->setSelectionMode(Wt::ExtendedSelection); + this->setSortingEnabled(true); + this->setAlternatingRowColors(true); + this->setModel(&_queryModel); + + this->setColumnWidth(1, 50); + this->setColumnWidth(2, 50); + + this->selectionChanged().connect(this, &TableFilterArtist::emitUpdate); + + setLayoutSizeAware(true); + + _queryModel.setBatchSize(100); + + // If an item is double clicked, select and emit signal + this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt) + { + if (!idx.isValid()) + return; + + Wt::WModelIndexSet indexSet; + indexSet.insert(idx); + + this->setSelectedIndexes( indexSet ); + _sigDoubleClicked.emit( ); + + }, std::placeholders::_1, std::placeholders::_2)); + +} + +void +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) +{ + Track::updateArtistQueryModel(_db.getSession(), _queryModel, filter); +} + +// Get constraint created by this filter +void +TableFilterArtist::getConstraint(SearchFilter& filter) +{ + Wt::WModelIndexSet indexSet = this->selectedIndexes(); + + BOOST_FOREACH(Wt::WModelIndex index, indexSet) { + + if (!index.isValid()) + continue; + + std::string name = _queryModel.resultRow( index.row() ).get<0>(); + + filter.exactMatch[Database::SearchFilter::Field::Artist].push_back(name); + } +} + +TableFilterRelease::TableFilterRelease(Database::Handler& db, Wt::WContainerWidget* parent) +: Wt::WTableView( parent ), Filter(), +_db(db) +{ + const std::vector columnNames = {"Release", "Date", "Tracks"}; + + SearchFilter filter; + + Track::updateReleaseQueryModel(_db.getSession(), _queryModel, filter, columnNames); + + this->setSelectionMode(Wt::ExtendedSelection); + this->setSortingEnabled(true); + this->setAlternatingRowColors(true); + this->setModel(&_queryModel); + + this->setColumnWidth(1, 50); + this->setColumnWidth(2, 50); + + // 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); + + setLayoutSizeAware(true); + + _queryModel.setBatchSize(100); + + // If an item is double clicked, select and emit signal + this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt) + { + if (!idx.isValid()) + return; + + Wt::WModelIndexSet indexSet; + indexSet.insert(idx); + + this->setSelectedIndexes( indexSet ); + _sigDoubleClicked.emit( ); + + }, std::placeholders::_1, std::placeholders::_2)); + +} + +void +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) +{ + Track::updateReleaseQueryModel(_db.getSession(), _queryModel, filter); +} + +// Get constraint created by this filter +void +TableFilterRelease::getConstraint(SearchFilter& filter) +{ + Wt::WModelIndexSet indexSet = this->selectedIndexes(); + + BOOST_FOREACH(Wt::WModelIndex index, indexSet) { + + if (!index.isValid()) + continue; + + std::string name = _queryModel.resultRow( index.row() ).get<0>(); + + filter.exactMatch[Database::SearchFilter::Field::Release].push_back(name); } } diff --git a/src/ui/audio/TableFilter.hpp b/src/ui/audio/TableFilter.hpp index 4f74dcd2..afac6866 100644 --- a/src/ui/audio/TableFilter.hpp +++ b/src/ui/audio/TableFilter.hpp @@ -29,11 +29,10 @@ namespace UserInterface { -class TableFilter : public Wt::WTableView, public Filter +class TableFilterGenre : public Wt::WTableView, public Filter { - public: - TableFilter(Database::Handler& db, Database::SearchFilter::Field field, std::vector displayName, Wt::WContainerWidget* parent = 0); + TableFilterGenre(Database::Handler& db, Wt::WContainerWidget* parent = 0); // Set constraints on this filter void refresh(Database::SearchFilter& filter); @@ -52,14 +51,66 @@ class TableFilter : public Wt::WTableView, public Filter SigDoubleClicked _sigDoubleClicked; Database::Handler& _db; - Database::SearchFilter::Field _field; // Name, track count - typedef boost::tuple ResultType; - Wt::Dbo::QueryModel< ResultType > _queryModel; - + Wt::Dbo::QueryModel _queryModel; }; +class TableFilterArtist : public Wt::WTableView, public Filter +{ + public: + TableFilterArtist(Database::Handler& db, 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; + + Database::Handler& _db; + + // Name, track count + Wt::Dbo::QueryModel _queryModel; +}; + +class TableFilterRelease : public Wt::WTableView, public Filter +{ + public: + TableFilterRelease(Database::Handler& db, 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; + + Database::Handler& _db; + + // Name, track count + Wt::Dbo::QueryModel _queryModel; +}; + + } // namespace UserInterface #endif