diff --git a/data/data.qrc b/data/data.qrc index a00dd700e..4d49d45c6 100644 --- a/data/data.qrc +++ b/data/data.qrc @@ -471,6 +471,7 @@ schema/schema-4.sql schema/schema-5.sql schema/schema-50.sql + schema/schema-51.sql schema/schema-6.sql schema/schema-7.sql schema/schema-8.sql diff --git a/data/schema/schema-51.sql b/data/schema/schema-51.sql new file mode 100644 index 000000000..3890f3831 --- /dev/null +++ b/data/schema/schema-51.sql @@ -0,0 +1,15 @@ +DELETE FROM %allsongstables_fts; + +DROP TABLE %allsongstables_fts; + +CREATE VIRTUAL TABLE %allsongstables_fts USING fts3( ftstitle, ftsalbum, ftsartist, ftsalbumartist, + ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment, ftsyear, + tokenize=unicode +); + +INSERT INTO %allsongstables_fts ( ROWID, ftstitle, ftsalbum, ftsartist, ftsalbumartist, + ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment, ftsyear) + SELECT ROWID, title, album, artist, albumartist, composer, performer, grouping, genre, comment, year + FROM %allsongstables; + +UPDATE schema_version SET version=51; \ No newline at end of file diff --git a/src/core/database.cpp b/src/core/database.cpp index 86e8a1052..4b9efada1 100644 --- a/src/core/database.cpp +++ b/src/core/database.cpp @@ -47,7 +47,7 @@ #include const char* Database::kDatabaseFilename = "clementine.db"; -const int Database::kSchemaVersion = 50; +const int Database::kSchemaVersion = 51; const char* Database::kMagicAllSongsTables = "%allsongstables"; int Database::sNextConnectionId = 1; diff --git a/src/core/song.cpp b/src/core/song.cpp index 0f6026fc1..d2e55e150 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -130,7 +130,8 @@ const QStringList Song::kFtsColumns = QStringList() << "ftstitle" << "ftsperformer" << "ftsgrouping" << "ftsgenre" - << "ftscomment"; + << "ftscomment" + << "ftsyear"; const QString Song::kFtsColumnSpec = Song::kFtsColumns.join(", "); const QString Song::kFtsBindSpec = @@ -996,6 +997,7 @@ void Song::BindToFtsQuery(QSqlQuery* query) const { query->bindValue(":ftsgrouping", d->grouping_); query->bindValue(":ftsgenre", d->genre_); query->bindValue(":ftscomment", d->comment_); + query->bindValue(":ftsyear", d->year_); } #ifdef HAVE_LIBLASTFM diff --git a/src/globalsearch/globalsearchview.cpp b/src/globalsearch/globalsearchview.cpp index 7ab8258d8..7ae4f2c26 100644 --- a/src/globalsearch/globalsearchview.cpp +++ b/src/globalsearch/globalsearchview.cpp @@ -36,9 +36,11 @@ #include "core/logging.h" #include "core/mimedata.h" #include "core/timeconstants.h" +#include "internet/core/internetsongmimedata.h" #include "library/libraryfilterwidget.h" #include "library/librarymodel.h" #include "library/groupbydialog.h" +#include "playlist/songmimedata.h" using std::placeholders::_1; using std::placeholders::_2; @@ -446,34 +448,38 @@ bool GlobalSearchView::SearchKeyEvent(QKeyEvent* event) { } bool GlobalSearchView::ResultsContextMenuEvent(QContextMenuEvent* event) { - if (!context_menu_) { - context_menu_ = new QMenu(this); - context_actions_ << context_menu_->addAction( - IconLoader::Load("media-playback-start", IconLoader::Base), - tr("Append to current playlist"), this, - SLOT(AddSelectedToPlaylist())); - context_actions_ << context_menu_->addAction( - IconLoader::Load("media-playback-start", IconLoader::Base), - tr("Replace current playlist"), this, - SLOT(LoadSelected())); - context_actions_ << context_menu_->addAction( - IconLoader::Load("document-new", IconLoader::Base), - tr("Open in new playlist"), this, - SLOT(OpenSelectedInNewPlaylist())); + context_menu_ = new QMenu(this); + context_actions_ << context_menu_->addAction( + IconLoader::Load("media-playback-start", IconLoader::Base), + tr("Append to current playlist"), this, SLOT(AddSelectedToPlaylist())); + context_actions_ << context_menu_->addAction( + IconLoader::Load("media-playback-start", IconLoader::Base), + tr("Replace current playlist"), this, SLOT(LoadSelected())); + context_actions_ << context_menu_->addAction( + IconLoader::Load("document-new", IconLoader::Base), + tr("Open in new playlist"), this, SLOT(OpenSelectedInNewPlaylist())); - context_menu_->addSeparator(); - context_actions_ << context_menu_->addAction( - IconLoader::Load("go-next", IconLoader::Base), tr("Queue track"), - this, SLOT(AddSelectedToPlaylistEnqueue())); + context_menu_->addSeparator(); + context_actions_ << context_menu_->addAction( + IconLoader::Load("go-next", IconLoader::Base), tr("Queue track"), this, + SLOT(AddSelectedToPlaylistEnqueue())); - context_menu_->addSeparator(); - context_menu_->addMenu(tr("Group by")) - ->addActions(group_by_actions_->actions()); - context_menu_->addAction(IconLoader::Load("configure", IconLoader::Base), - tr("Configure global search..."), this, - SLOT(OpenSettingsDialog())); + context_menu_->addSeparator(); + + if (ui_->results->selectionModel() && + ui_->results->selectionModel()->selectedRows().length() == 1) { + context_actions_ << context_menu_->addAction( + IconLoader::Load("system-search", IconLoader::Base), + tr("Search for this"), this, SLOT(SearchForThis())); } + context_menu_->addSeparator(); + context_menu_->addMenu(tr("Group by")) + ->addActions(group_by_actions_->actions()); + context_menu_->addAction(IconLoader::Load("configure", IconLoader::Base), + tr("Configure global search..."), this, + SLOT(OpenSettingsDialog())); + const bool enable_context_actions = ui_->results->selectionModel() && ui_->results->selectionModel()->hasSelection(); @@ -515,6 +521,11 @@ void GlobalSearchView::OpenSelectedInNewPlaylist() { emit AddToPlaylist(data); } +void GlobalSearchView::SearchForThis() { + StartSearch( + ui_->results->selectionModel()->selectedRows().first().data().toString()); +} + void GlobalSearchView::showEvent(QShowEvent* e) { if (show_suggestions_) { UpdateSuggestions(); @@ -560,9 +571,12 @@ void GlobalSearchView::GroupByClicked(QAction* action) { } void GlobalSearchView::SetGroupBy(const LibraryModel::Grouping& g) { - // Clear requests: changing "group by" on the models will cause all the items to be removed/added - // again, so all the QModelIndex here will become invalid. New requests will be created for those - // songs when they will be displayed again anyway (when GlobalSearchItemDelegate::paint will call + // Clear requests: changing "group by" on the models will cause all the items + // to be removed/added + // again, so all the QModelIndex here will become invalid. New requests will + // be created for those + // songs when they will be displayed again anyway (when + // GlobalSearchItemDelegate::paint will call // LazyLoadArt) art_requests_.clear(); // Update the models diff --git a/src/globalsearch/globalsearchview.h b/src/globalsearch/globalsearchview.h index 4c376bdc5..2b530256b 100644 --- a/src/globalsearch/globalsearchview.h +++ b/src/globalsearch/globalsearchview.h @@ -21,6 +21,7 @@ #include "searchprovider.h" #include "library/librarymodel.h" #include "ui/settingsdialog.h" +#include "playlist/playlistmanager.h" #include @@ -82,6 +83,8 @@ signals: void OpenSelectedInNewPlaylist(); void AddSelectedToPlaylistEnqueue(); + void SearchForThis(); + void GroupByClicked(QAction* action); void SetGroupBy(const LibraryModel::Grouping& grouping); diff --git a/src/library/librarymodel.cpp b/src/library/librarymodel.cpp index e82f86630..ef13c3197 100644 --- a/src/library/librarymodel.cpp +++ b/src/library/librarymodel.cpp @@ -656,6 +656,9 @@ QVariant LibraryModel::data(const LibraryItem* item, int role) const { case Role_SortText: return item->SortText(); + + case Role_DisplayText: + return item->DisplayText(); } return QVariant(); } diff --git a/src/library/librarymodel.h b/src/library/librarymodel.h index bef9e75ee..00463624c 100644 --- a/src/library/librarymodel.h +++ b/src/library/librarymodel.h @@ -64,6 +64,7 @@ class LibraryModel : public SimpleTreeModel { Role_Type = Qt::UserRole + 1, Role_ContainerType, Role_SortText, + Role_DisplayText, Role_Key, Role_Artist, Role_IsDivider, diff --git a/src/library/libraryview.cpp b/src/library/libraryview.cpp index 04c0f438d..1fbe3c4b8 100644 --- a/src/library/libraryview.cpp +++ b/src/library/libraryview.cpp @@ -103,7 +103,7 @@ void LibraryItemDelegate::paint(QPainter* painter, // Draw the line under the item QColor line_color = opt.palette.color(QPalette::Text); QLinearGradient grad_color(opt.rect.bottomLeft(), opt.rect.bottomRight()); - const double fade_start_end = (opt.rect.width()/3.0)/opt.rect.width(); + const double fade_start_end = (opt.rect.width() / 3.0) / opt.rect.width(); line_color.setAlphaF(0.0); grad_color.setColorAt(0, line_color); line_color.setAlphaF(0.5); @@ -193,15 +193,17 @@ LibraryView::~LibraryView() {} void LibraryView::SaveFocus() { QModelIndex current = currentIndex(); QVariant type = model()->data(current, LibraryModel::Role_Type); - if (!type.isValid() || !(type.toInt() == LibraryItem::Type_Song || - type.toInt() == LibraryItem::Type_Container || - type.toInt() == LibraryItem::Type_Divider)) { + if (!type.isValid() || + !(type.toInt() == LibraryItem::Type_Song || + type.toInt() == LibraryItem::Type_Container || + type.toInt() == LibraryItem::Type_Divider)) { return; } last_selected_path_.clear(); last_selected_song_ = Song(); last_selected_container_ = QString(); + last_selected_text_ = QString(); switch (type.toInt()) { case LibraryItem::Type_Song: { @@ -210,6 +212,7 @@ void LibraryView::SaveFocus() { SongList songs = app_->library_model()->GetChildSongs(index); if (!songs.isEmpty()) { last_selected_song_ = songs.last(); + last_selected_text_ = songs.last().title(); } break; } @@ -217,8 +220,9 @@ void LibraryView::SaveFocus() { case LibraryItem::Type_Container: case LibraryItem::Type_Divider: { QString text = - model()->data(current, LibraryModel::Role_SortText).toString(); + model()->data(current, LibraryModel::Role_Key).toString(); last_selected_container_ = text; + last_selected_text_ = model()->data(current, LibraryModel::Role_DisplayText).toString(); break; } @@ -232,8 +236,9 @@ void LibraryView::SaveFocus() { void LibraryView::SaveContainerPath(const QModelIndex& child) { QModelIndex current = model()->parent(child); QVariant type = model()->data(current, LibraryModel::Role_Type); - if (!type.isValid() || !(type.toInt() == LibraryItem::Type_Container || - type.toInt() == LibraryItem::Type_Divider)) { + if (!type.isValid() || + !(type.toInt() == LibraryItem::Type_Container || + type.toInt() == LibraryItem::Type_Divider)) { return; } @@ -276,7 +281,7 @@ bool LibraryView::RestoreLevelFocus(const QModelIndex& parent) { case LibraryItem::Type_Container: case LibraryItem::Type_Divider: { QString text = - model()->data(current, LibraryModel::Role_SortText).toString(); + model()->data(current, LibraryModel::Role_Key).toString(); if (!last_selected_container_.isEmpty() && last_selected_container_ == text) { emit expand(current); @@ -384,11 +389,13 @@ void LibraryView::contextMenuEvent(QContextMenuEvent* e) { tr("Open in new playlist"), this, SLOT(OpenInNewPlaylist())); context_menu_->addSeparator(); - add_to_playlist_enqueue_ = - context_menu_->addAction(IconLoader::Load("go-next", IconLoader::Base), - tr("Queue track"), this, - SLOT(AddToPlaylistEnqueue())); - + add_to_playlist_enqueue_ = context_menu_->addAction( + IconLoader::Load("go-next", IconLoader::Base), tr("Queue track"), this, + SLOT(AddToPlaylistEnqueue())); + context_menu_->addSeparator(); + search_for_this_ = context_menu_->addAction( + IconLoader::Load("system-search", IconLoader::Base), + tr("Search for this"), this, SLOT(SearchForThis())); context_menu_->addSeparator(); new_smart_playlist_ = context_menu_->addAction( IconLoader::Load("document-new", IconLoader::Base), @@ -401,23 +408,23 @@ void LibraryView::contextMenuEvent(QContextMenuEvent* e) { tr("Delete smart playlist"), this, SLOT(DeleteSmartPlaylist())); context_menu_->addSeparator(); - organise_ = context_menu_->addAction(IconLoader::Load("edit-copy", IconLoader::Base), - tr("Organise files..."), this, - SLOT(Organise())); + organise_ = context_menu_->addAction( + IconLoader::Load("edit-copy", IconLoader::Base), + tr("Organise files..."), this, SLOT(Organise())); copy_to_device_ = context_menu_->addAction( IconLoader::Load("multimedia-player-ipod-mini-blue", IconLoader::Base), tr("Copy to device..."), this, SLOT(CopyToDevice())); - delete_ = context_menu_->addAction(IconLoader::Load("edit-delete", IconLoader::Base), - tr("Delete from disk..."), this, - SLOT(Delete())); + delete_ = context_menu_->addAction( + IconLoader::Load("edit-delete", IconLoader::Base), + tr("Delete from disk..."), this, SLOT(Delete())); context_menu_->addSeparator(); - edit_track_ = context_menu_->addAction(IconLoader::Load("edit-rename", IconLoader::Base), - tr("Edit track information..."), - this, SLOT(EditTracks())); - edit_tracks_ = context_menu_->addAction(IconLoader::Load("edit-rename", IconLoader::Base), - tr("Edit tracks information..."), - this, SLOT(EditTracks())); + edit_track_ = context_menu_->addAction( + IconLoader::Load("edit-rename", IconLoader::Base), + tr("Edit track information..."), this, SLOT(EditTracks())); + edit_tracks_ = context_menu_->addAction( + IconLoader::Load("edit-rename", IconLoader::Base), + tr("Edit tracks information..."), this, SLOT(EditTracks())); show_in_browser_ = context_menu_->addAction( IconLoader::Load("document-open-folder", IconLoader::Base), tr("Show in file browser..."), this, SLOT(ShowInBrowser())); @@ -458,6 +465,8 @@ void LibraryView::contextMenuEvent(QContextMenuEvent* e) { int regular_elements = 0; // number of editable non smart playlists selected int regular_editable = 0; + // number of container elements selected + int container_elements = 0; for (const QModelIndex& index : selected_indexes) { int type = @@ -467,6 +476,10 @@ void LibraryView::contextMenuEvent(QContextMenuEvent* e) { smart_playlists++; } else if (type == LibraryItem::Type_PlaylistContainer) { smart_playlists_header++; + } else if (type == LibraryItem::Type_Container) { + container_elements++; + // To preserve expected behavior, since a container is "regular" + regular_elements++; } else { regular_elements++; } @@ -487,6 +500,10 @@ void LibraryView::contextMenuEvent(QContextMenuEvent* e) { songs_selected == smart_playlists + smart_playlists_header; const bool only_smart_playlist_selected = smart_playlists == 1 && songs_selected == 1; + const bool one_regular_song_only = + regular_elements_only && container_elements == 0 && regular_elements == 1; + const bool one_container_only = + container_elements == 1 && songs_selected == 1; // in all modes load_->setEnabled(songs_selected); @@ -509,6 +526,9 @@ void LibraryView::contextMenuEvent(QContextMenuEvent* e) { show_in_various_->setVisible(regular_elements_only); no_show_in_various_->setVisible(regular_elements_only); + // only when a single container or one song is selected exclusively + search_for_this_->setVisible(one_container_only || one_regular_song_only); + // only when all selected items are editable organise_->setEnabled(regular_elements == regular_editable); copy_to_device_->setEnabled(regular_elements == regular_editable); @@ -714,6 +734,13 @@ void LibraryView::FilterReturnPressed() { emit doubleClicked(currentIndex()); } +void LibraryView::SearchForThis() { + SaveFocus(); + if (!last_selected_text_.isEmpty()) { + filter_->ShowInLibrary(last_selected_text_.simplified()); + } +} + void LibraryView::NewSmartPlaylist() { Wizard* wizard = new Wizard(app_, app_->library_backend(), this); wizard->setAttribute(Qt::WA_DeleteOnClose); diff --git a/src/library/libraryview.h b/src/library/libraryview.h index 3fbf0cb93..a747d0aa0 100644 --- a/src/library/libraryview.h +++ b/src/library/libraryview.h @@ -101,6 +101,8 @@ signals: void ShowInVarious(); void NoShowInVarious(); + void SearchForThis(); + void NewSmartPlaylist(); void EditSmartPlaylist(); void DeleteSmartPlaylist(); @@ -139,6 +141,8 @@ signals: QAction* show_in_various_; QAction* no_show_in_various_; + QAction* search_for_this_; + QAction* new_smart_playlist_; QAction* edit_smart_playlist_; QAction* delete_smart_playlist_; @@ -151,6 +155,7 @@ signals: // Save focus Song last_selected_song_; QString last_selected_container_; + QString last_selected_text_; QSet last_selected_path_; }; diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index 7c93fa88b..f40340c42 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -676,7 +676,13 @@ MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd, ui_->playlist->addAction(playlist_queue_); playlist_skip_ = playlist_menu_->addAction("", this, SLOT(PlaylistSkip())); ui_->playlist->addAction(playlist_skip_); - + playlist_menu_->addSeparator(); + search_for_artist_ = playlist_menu_->addAction( + IconLoader::Load("system-search", IconLoader::Base), + tr("Search for artist"), this, SLOT(SearchForArtist())); + search_for_album_ = playlist_menu_->addAction( + IconLoader::Load("system-search", IconLoader::Base), + tr("Search for album"), this, SLOT(SearchForAlbum())); playlist_menu_->addSeparator(); playlist_menu_->addAction(ui_->action_remove_from_playlist); playlist_undoredo_ = playlist_menu_->addSeparator(); @@ -1733,6 +1739,9 @@ void MainWindow::PlaylistRightClick(const QPoint& global_pos, playlist_delete_->setVisible(false); playlist_copy_to_device_->setVisible(false); + search_for_artist_->setVisible(all == 1); + search_for_album_->setVisible(all == 1); + if (in_queue == 1 && not_in_queue == 0) playlist_queue_->setText(tr("Dequeue track")); else if (in_queue > 1 && not_in_queue == 0) @@ -2484,6 +2493,26 @@ void MainWindow::PlaylistCopyToDevice() { } } +void MainWindow::SearchForArtist() { + PlaylistItemPtr item( + app_->playlist_manager()->current()->item_at(playlist_menu_index_.row())); + Song song = item->Metadata(); + if (!song.albumartist().isEmpty()) { + DoGlobalSearch(song.albumartist().simplified()); + } else if (!song.artist().isEmpty()) { + DoGlobalSearch(song.artist().simplified()); + } +} + +void MainWindow::SearchForAlbum() { + PlaylistItemPtr item( + app_->playlist_manager()->current()->item_at(playlist_menu_index_.row())); + Song song = item->Metadata(); + if (!song.album().isEmpty()) { + DoGlobalSearch(song.album().simplified()); + } +} + void MainWindow::ChangeLibraryQueryMode(QAction* action) { if (action == library_show_duplicates_) { library_view_->filter()->SetQueryMode(QueryOptions::QueryMode_Duplicates); diff --git a/src/ui/mainwindow.h b/src/ui/mainwindow.h index d796d39bb..03709ed8a 100644 --- a/src/ui/mainwindow.h +++ b/src/ui/mainwindow.h @@ -147,7 +147,6 @@ signals: void StopAfterToggled(bool stop); void IntroPointReached(); - private slots: void FilePathChanged(const QString& path); @@ -178,6 +177,9 @@ signals: void PlaylistUndoRedoChanged(QAction* undo, QAction* redo); void AddFilesToTranscoder(); + void SearchForArtist(); + void SearchForAlbum(); + void PlaylistCopyToLibrary(); void PlaylistMoveToLibrary(); void PlaylistCopyToDevice(); @@ -365,6 +367,9 @@ signals: QAction* playlistitem_actions_separator_; QModelIndex playlist_menu_index_; + QAction* search_for_artist_; + QAction* search_for_album_; + QSortFilterProxyModel* library_sort_model_; QTimer* track_position_timer_;