From d790a0cf763c1602614a0b6b596fa77946c4f556 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 21 Dec 2014 14:42:41 +0100 Subject: [PATCH 1/5] [UI] Added clear button in the play queue. Corrected some style stuff too --- src/ui/audio/Audio.cpp | 11 +++++++---- src/ui/audio/PlayQueue.cpp | 6 ++++++ src/ui/audio/PlayQueue.hpp | 1 + src/ui/docroot/css/lms.css | 8 ++++++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/ui/audio/Audio.cpp b/src/ui/audio/Audio.cpp index 32274ac3..d1675ff9 100644 --- a/src/ui/audio/Audio.cpp +++ b/src/ui/audio/Audio.cpp @@ -44,6 +44,7 @@ _playQueue(nullptr) { Wt::WGridLayout *mainLayout = new Wt::WGridLayout(); this->setLayout(mainLayout); + mainLayout->setContentsMargins(9,4,9,9); // Filters Wt::WHBoxLayout *filterLayout = new Wt::WHBoxLayout(); @@ -122,14 +123,16 @@ _playQueue(nullptr) downBtn->setStyleClass("btn-sm"); playlistControls->addWidget(downBtn); Wt::WPushButton *delBtn = new Wt::WPushButton("DEL"); - delBtn->setStyleClass("btn-sm"); + delBtn->setStyleClass("btn-sm btn-warning"); playlistControls->addWidget(delBtn); + Wt::WPushButton *clearBtn = new Wt::WPushButton("CLR"); + 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); - - playlistControls->addWidget(new Wt::WText("Duration:"), 1); + clearBtn->clicked().connect(_playQueue, &PlayQueue::delAll); // Load menu { @@ -160,7 +163,7 @@ _playQueue(nullptr) mainLayout->setRowStretch(1, 1); mainLayout->setRowResizable(0, true, Wt::WLength(200, Wt::WLength::Pixel)); - mainLayout->setColumnResizable(0, true, Wt::WLength(400, Wt::WLength::Pixel)); + mainLayout->setColumnResizable(0, true, Wt::WLength(350, Wt::WLength::Pixel)); // Double click on track // Set the selected tracks to the play queue diff --git a/src/ui/audio/PlayQueue.cpp b/src/ui/audio/PlayQueue.cpp index ed392bad..13f313e3 100644 --- a/src/ui/audio/PlayQueue.cpp +++ b/src/ui/audio/PlayQueue.cpp @@ -502,7 +502,13 @@ PlayQueue::delSelected(void) _trackSelector->setSize(_model->rowCount()); renumber(minId, _model->rowCount() - 1); +} +void +PlayQueue::delAll(void) +{ + _model->removeRows(0, _model->rowCount()); + _trackSelector->setSize(0); } void diff --git a/src/ui/audio/PlayQueue.hpp b/src/ui/audio/PlayQueue.hpp index 1f968353..003e8518 100644 --- a/src/ui/audio/PlayQueue.hpp +++ b/src/ui/audio/PlayQueue.hpp @@ -53,6 +53,7 @@ class PlayQueue : public Wt::WTableView // List manipulations void delSelected(void); + void delAll(void); void moveSelectedUp(void); void moveSelectedDown(void); diff --git a/src/ui/docroot/css/lms.css b/src/ui/docroot/css/lms.css index 7b8dfaa2..23c78024 100644 --- a/src/ui/docroot/css/lms.css +++ b/src/ui/docroot/css/lms.css @@ -20,15 +20,19 @@ div.contents { } .playqueue-playing { - background-color: rgba(202, 167, 66, 1); + background-color: #4A42CA; } .Wt-tableview .playqueue-playing { color: #FFF; } +.Wt-tableview div.playqueue-playing { + background-color: #4A42CA; +} + .Wt-tableview .Wt-tv-contents.Wt-striped div.playqueue-playing:nth-child(odd) { - background-color: rgba(202, 167, 66, 1); + background-color: #4A42CA; } .playqueue-track { From 02aa3fd601fdf328543b845327624e94d8568b98 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 21 Dec 2014 14:50:44 +0100 Subject: [PATCH 2/5] Updated TODO --- TODO | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/TODO b/TODO index e956e043..1b1fd9ae 100644 --- a/TODO +++ b/TODO @@ -19,6 +19,7 @@ - count scan import errors [Metadata] +- OGG metadata -> properly handle metadata nested in the audio stream [Transcode] - some early end of playback spotted on flac files @@ -39,24 +40,18 @@ [Audio] - Style eveything nicely... - - ReleaseView/ArtistView/GenreView: -> Track count for this special category. Easier: add a special entry '' that contains everything? - MediaPlayer: move slider using js (http://redmine.webtoolkit.eu/boards/2/topics/7924?r=8478) - TrackView : handle duration > 1 hour - - TrackView : select only relevant columns to speed up queries (do not fetch eveything) - TrackView : Reselect the current selected item when displaying the updated search results - - OGG metadata -> properly handle metadata nested in the audio stream - - Filters: use directly the id as constraint, instead of the name? - - Implement a playlist: - - Drag and drop all objets from the views (genre, artist, release, track) in the playlist? - - Add 'play/add last' button on mouse hover? - - Save/Load playlists? + - Add Year column in the release filter. Add covers too? + - Save/Load playlists [Video] - - View the Videos in a WtTableView ? - - Use Groups/SubGroups to organize videos (WTreeView?) + - implement a decent mediaplayer + - remove codec choice from parameters [Layout] - - Make it mobile browser compatible + - Make a dedicated audio layout in mobile environment (keep the same layout for videos) - Style eveything nicely... - Handle internal path From 8d17bf37856d0108eff86139ebe20d294421127a Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 23 Dec 2014 16:55:57 +0100 Subject: [PATCH 3/5] [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 From 78bfd3a1862ca7f9c41b0f1d900a7208da7944ca Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 6 Jan 2015 13:33:36 +0100 Subject: [PATCH 4/5] [UI] Dynamically launch searches while filling in the field --- src/Makefile.am | 1 + src/ui/LmsApplication.cpp | 15 ++++++----- src/ui/common/LineEdit.cpp | 55 ++++++++++++++++++++++++++++++++++++++ src/ui/common/LineEdit.hpp | 44 ++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 src/ui/common/LineEdit.cpp create mode 100644 src/ui/common/LineEdit.hpp diff --git a/src/Makefile.am b/src/Makefile.am index b8480855..ada8b7ef 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -47,6 +47,7 @@ lms_SOURCES = \ $(srcdir)/ui/audio/TableFilter.cpp \ $(srcdir)/ui/audio/TrackView.cpp \ $(srcdir)/ui/common/DirectoryValidator.cpp \ + $(srcdir)/ui/common/LineEdit.cpp \ $(srcdir)/ui/common/SessionData.cpp \ $(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \ $(srcdir)/ui/resource/CoverResource.cpp \ diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index ad803ae6..ba7cfc42 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -30,16 +30,15 @@ #include #include -#include "settings/Settings.hpp" - -#include "auth/LmsAuth.hpp" - #include "logger/Logger.hpp" +#include "settings/Settings.hpp" #include "settings/SettingsFirstConnectionFormView.hpp" +#include "auth/LmsAuth.hpp" #include "audio/Audio.hpp" #include "video/VideoWidget.hpp" +#include "common/LineEdit.hpp" #include "LmsApplication.hpp" @@ -133,6 +132,8 @@ LmsApplication::handleAuthEvent(void) LMS_LOG(MOD_UI, SEV_NOTICE) << "User '" << user.identity(Wt::Auth::Identity::LoginName) << "' logged in"; + this->root()->setOverflow(Wt::WContainerWidget::OverflowHidden); + // Create a Vertical layout: top is the nav bar, bottom is the contents Wt::WVBoxLayout *layout = new Wt::WVBoxLayout(this->root()); // Create a navigation bar with a link to a web page. @@ -143,6 +144,8 @@ LmsApplication::handleAuthEvent(void) Wt::WStackedWidget *contentsStack = new Wt::WStackedWidget(); + contentsStack->setOverflow(Wt::WContainerWidget::OverflowAuto); + // Setup a Left-aligned menu. Wt::WMenu *leftMenu = new Wt::WMenu(contentsStack); navigation->addMenu(leftMenu); @@ -173,10 +176,10 @@ LmsApplication::handleAuthEvent(void) }, std::placeholders::_1)); // Add a Search control. - Wt::WLineEdit *searchEdit = new Wt::WLineEdit(); + LineEdit *searchEdit = new LineEdit(500); searchEdit->setEmptyText("Search..."); - searchEdit->enterPressed().connect(std::bind([=] () + searchEdit->timedChanged().connect(std::bind([=] () { // TODO: check which view is activated and search into it audio->search(searchEdit->text().toUTF8()); diff --git a/src/ui/common/LineEdit.cpp b/src/ui/common/LineEdit.cpp new file mode 100644 index 00000000..d5f21fda --- /dev/null +++ b/src/ui/common/LineEdit.cpp @@ -0,0 +1,55 @@ +/* + * 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 "LineEdit.hpp" + +namespace UserInterface { + + +LineEdit::LineEdit(std::size_t ms, Wt::WContainerWidget* parent) +: Wt::WLineEdit(parent) +{ + + Wt::WTimer *timer = new Wt::WTimer(this); + timer->setSingleShot(true); + timer->setInterval(ms); + + this->keyWentUp().connect(std::bind([=] (Wt::WKeyEvent keyEvent) + { + if (timer->isActive()) + timer->stop(); + + if (keyEvent.key() == Wt::Key_Enter) + _sigTimedChanged.emit(this->text()); + else + timer->start(); + }, std::placeholders::_1)); + + timer->timeout().connect(std::bind([=] () + { + _sigTimedChanged.emit(this->text()); + })); +} + + +} // namespace UserInterface + + diff --git a/src/ui/common/LineEdit.hpp b/src/ui/common/LineEdit.hpp new file mode 100644 index 00000000..9d27c43a --- /dev/null +++ b/src/ui/common/LineEdit.hpp @@ -0,0 +1,44 @@ +/* + * 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_LINE_EDIT_HPP +#define UI_LINE_EDIT_HPP + +#include +#include +#include + +namespace UserInterface { + +class LineEdit : public Wt::WLineEdit +{ + public: + LineEdit(std::size_t ms, Wt::WContainerWidget* parent = 0); + + Wt::Signal& timedChanged() { return _sigTimedChanged; } + + private: + + Wt::Signal _sigTimedChanged; +}; + +} // namespace UserInterface + +#endif + From 2d110b02804c0bfc43fa9602ac04048337a3e60e Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 6 Jan 2015 23:15:25 +0100 Subject: [PATCH 5/5] [UI] Adjusted some column widths --- src/ui/audio/Audio.cpp | 4 ++-- src/ui/audio/TableFilter.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ui/audio/Audio.cpp b/src/ui/audio/Audio.cpp index c55f9bae..d3e337ef 100644 --- a/src/ui/audio/Audio.cpp +++ b/src/ui/audio/Audio.cpp @@ -162,8 +162,8 @@ _playQueue(nullptr) } mainLayout->setRowStretch(1, 1); - mainLayout->setRowResizable(0, true, Wt::WLength(200, Wt::WLength::Pixel)); - mainLayout->setColumnResizable(0, true, Wt::WLength(350, Wt::WLength::Pixel)); + 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 diff --git a/src/ui/audio/TableFilter.cpp b/src/ui/audio/TableFilter.cpp index 83c83648..00ab482e 100644 --- a/src/ui/audio/TableFilter.cpp +++ b/src/ui/audio/TableFilter.cpp @@ -46,7 +46,7 @@ _db(db) this->setAlternatingRowColors(true); this->setModel(&_queryModel); - this->setColumnWidth(1, 50); + this->setColumnWidth(1, 80); this->selectionChanged().connect(this, &TableFilterGenre::emitUpdate); @@ -117,8 +117,8 @@ _db(db) this->setAlternatingRowColors(true); this->setModel(&_queryModel); - this->setColumnWidth(1, 50); - this->setColumnWidth(2, 50); + this->setColumnWidth(1, 80); + this->setColumnWidth(2, 80); this->selectionChanged().connect(this, &TableFilterArtist::emitUpdate); @@ -189,8 +189,8 @@ _db(db) this->setAlternatingRowColors(true); this->setModel(&_queryModel); - this->setColumnWidth(1, 50); - this->setColumnWidth(2, 50); + this->setColumnWidth(1, 60); + this->setColumnWidth(2, 80); // Date display, just the year {