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_;