Show some suggested queries in the global search widget
This commit is contained in:
parent
f79d38210c
commit
63f39d8dec
@ -25,7 +25,7 @@ DigitallyImportedSearchProvider::DigitallyImportedSearchProvider(
|
||||
service_(service)
|
||||
{
|
||||
Init(service_->name(), service->api_service_name(), service_->icon(),
|
||||
ArtIsInSongMetadata);
|
||||
ArtIsInSongMetadata | CanGiveSuggestions);
|
||||
|
||||
set_safe_words(QStringList() << "sky.fm" << "skyfm" << "di.fm" << "difm"
|
||||
<< "digitallyimported");
|
||||
|
@ -318,3 +318,23 @@ void GlobalSearch::SaveProvidersSettings() {
|
||||
s.setValue("enabled_" + provider->id(), providers_[provider].enabled_);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList GlobalSearch::GetSuggestions(int max) {
|
||||
QStringList ret;
|
||||
QList<SearchProvider*> eligible_providers;
|
||||
|
||||
foreach (SearchProvider* provider, providers_.keys()) {
|
||||
if (is_provider_enabled(provider) && provider->can_give_suggestions()) {
|
||||
eligible_providers << provider;
|
||||
}
|
||||
}
|
||||
|
||||
while (ret.count() < max && !eligible_providers.isEmpty()) {
|
||||
SearchProvider* provider = eligible_providers.takeAt(qrand() % eligible_providers.count());
|
||||
QString suggestion = provider->GetSuggestion().trimmed();
|
||||
if (!suggestion.isEmpty())
|
||||
ret << suggestion;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ public:
|
||||
int SearchAsync(const QString& query);
|
||||
int LoadArtAsync(const SearchProvider::Result& result);
|
||||
int LoadTracksAsync(const SearchProvider::Result& result);
|
||||
QStringList GetSuggestions(int max);
|
||||
|
||||
void CancelSearch(int id);
|
||||
void CancelArt(int id);
|
||||
|
@ -44,6 +44,8 @@
|
||||
const int GlobalSearchWidget::kMinVisibleItems = 3;
|
||||
const int GlobalSearchWidget::kMaxVisibleItems = 25;
|
||||
const int GlobalSearchWidget::kSwapModelsTimeoutMsec = 250;
|
||||
const int GlobalSearchWidget::kSuggestionTimeoutMsec = 60000; // 1 minute
|
||||
const int GlobalSearchWidget::kSuggestionCount = 3;
|
||||
|
||||
|
||||
GlobalSearchWidget::GlobalSearchWidget(QWidget* parent)
|
||||
@ -64,7 +66,8 @@ GlobalSearchWidget::GlobalSearchWidget(QWidget* parent)
|
||||
background_(":allthethings.png"),
|
||||
desktop_(qApp->desktop()),
|
||||
show_tooltip_(true),
|
||||
combine_identical_results_(true)
|
||||
combine_identical_results_(true),
|
||||
next_suggestion_timer_(new QTimer(this))
|
||||
{
|
||||
ui_->setupUi(this);
|
||||
ReloadSettings();
|
||||
@ -124,12 +127,16 @@ GlobalSearchWidget::GlobalSearchWidget(QWidget* parent)
|
||||
swap_models_timer_->setSingleShot(true);
|
||||
swap_models_timer_->setInterval(kSwapModelsTimeoutMsec);
|
||||
|
||||
next_suggestion_timer_->setInterval(kSuggestionTimeoutMsec);
|
||||
hint_text_ = ui_->search->hint();
|
||||
|
||||
connect(ui_->search, SIGNAL(textEdited(QString)), SLOT(TextEdited(QString)));
|
||||
connect(view_, SIGNAL(doubleClicked(QModelIndex)), SLOT(ResultDoubleClicked()));
|
||||
connect(view_->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
|
||||
SLOT(UpdateTooltip()));
|
||||
connect(swap_models_timer_, SIGNAL(timeout()), SLOT(SwapModels()));
|
||||
connect(ui_->settings, SIGNAL(clicked()), SLOT(SettingsClicked()));
|
||||
connect(next_suggestion_timer_, SIGNAL(timeout()), SLOT(NextSuggestion()));
|
||||
}
|
||||
|
||||
GlobalSearchWidget::~GlobalSearchWidget() {
|
||||
@ -196,6 +203,19 @@ void GlobalSearchWidget::paintEvent(QPaintEvent* e) {
|
||||
p.drawLine(total_rect.bottomLeft(), total_rect.bottomRight());
|
||||
}
|
||||
|
||||
void GlobalSearchWidget::hideEvent(QHideEvent* e) {
|
||||
QWidget::hideEvent(e);
|
||||
|
||||
next_suggestion_timer_->stop();
|
||||
}
|
||||
|
||||
void GlobalSearchWidget::showEvent(QShowEvent* e) {
|
||||
QWidget::showEvent(e);
|
||||
|
||||
next_suggestion_timer_->start();
|
||||
NextSuggestion();
|
||||
}
|
||||
|
||||
void GlobalSearchWidget::TextEdited(const QString& text) {
|
||||
const QString trimmed_text = text.trimmed();
|
||||
|
||||
@ -670,3 +690,14 @@ void GlobalSearchWidget::UpdateTooltip() {
|
||||
void GlobalSearchWidget::SettingsClicked() {
|
||||
emit OpenSettingsAtPage(SettingsDialog::Page_GlobalSearch);
|
||||
}
|
||||
|
||||
void GlobalSearchWidget::NextSuggestion() {
|
||||
const QStringList suggestions = engine_->GetSuggestions(kSuggestionCount);
|
||||
QString hint = hint_text_;
|
||||
|
||||
if (!suggestions.isEmpty()) {
|
||||
hint += ", e.g. " + suggestions.join(", ");
|
||||
}
|
||||
|
||||
ui_->search->set_hint(hint);
|
||||
}
|
||||
|
@ -50,6 +50,8 @@ public:
|
||||
static const int kMinVisibleItems;
|
||||
static const int kMaxVisibleItems;
|
||||
static const int kSwapModelsTimeoutMsec;
|
||||
static const int kSuggestionTimeoutMsec;
|
||||
static const int kSuggestionCount;
|
||||
|
||||
enum Role {
|
||||
Role_PrimaryResult = Qt::UserRole + 1,
|
||||
@ -63,7 +65,7 @@ public:
|
||||
// Called by the delegate
|
||||
void LazyLoadArt(const QModelIndex& index);
|
||||
|
||||
// QWidget
|
||||
// QObject
|
||||
bool eventFilter(QObject* o, QEvent* e);
|
||||
|
||||
public slots:
|
||||
@ -76,6 +78,8 @@ signals:
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* e);
|
||||
void paintEvent(QPaintEvent* e);
|
||||
void showEvent(QShowEvent* e);
|
||||
void hideEvent(QHideEvent* e);
|
||||
|
||||
private slots:
|
||||
void TextEdited(const QString& text);
|
||||
@ -98,6 +102,8 @@ private slots:
|
||||
|
||||
void SwapModels();
|
||||
|
||||
void NextSuggestion();
|
||||
|
||||
private:
|
||||
// Return values from CanCombineResults
|
||||
enum CombineAction {
|
||||
@ -159,6 +165,9 @@ private:
|
||||
QAction* replace_;
|
||||
QAction* replace_and_play_;
|
||||
QList<QAction*> actions_;
|
||||
|
||||
QString hint_text_;
|
||||
QTimer* next_suggestion_timer_;
|
||||
};
|
||||
|
||||
#endif // GLOBALSEARCHWIDGET_H
|
||||
|
@ -17,7 +17,7 @@
|
||||
<item>
|
||||
<widget class="LineEdit" name="search">
|
||||
<property name="hint" stdset="0">
|
||||
<string>Global search</string>
|
||||
<string>Search for anything</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -23,7 +23,8 @@
|
||||
LastFMSearchProvider::LastFMSearchProvider(LastFMService* service, QObject* parent)
|
||||
: SimpleSearchProvider(parent),
|
||||
service_(service) {
|
||||
Init("Last.fm", "lastfm", QIcon(":last.fm/as.png"), CanShowConfig);
|
||||
Init("Last.fm", "lastfm", QIcon(":last.fm/as.png"),
|
||||
CanShowConfig | CanGiveSuggestions);
|
||||
icon_ = ScaleAndPad(QImage(":last.fm/as.png"));
|
||||
|
||||
set_safe_words(QStringList() << "lastfm" << "last.fm");
|
||||
|
@ -32,7 +32,8 @@ LibrarySearchProvider::LibrarySearchProvider(LibraryBackendInterface* backend,
|
||||
: BlockingSearchProvider(parent),
|
||||
backend_(backend)
|
||||
{
|
||||
Init(name, id, icon, WantsSerialisedArtQueries | ArtIsInSongMetadata);
|
||||
Init(name, id, icon, WantsSerialisedArtQueries | ArtIsInSongMetadata |
|
||||
CanGiveSuggestions);
|
||||
}
|
||||
|
||||
SearchProvider::ResultList LibrarySearchProvider::Search(int id, const QString& query) {
|
||||
@ -146,3 +147,45 @@ void LibrarySearchProvider::LoadTracksAsync(int id, const Result& result) {
|
||||
|
||||
emit TracksLoaded(id, mime_data);
|
||||
}
|
||||
|
||||
QString LibrarySearchProvider::GetSuggestion() {
|
||||
// We'd like to use order by random(), but that's O(n) in sqlite, so instead
|
||||
// get the largest ROWID and pick a couple of random numbers within that
|
||||
// range.
|
||||
|
||||
LibraryQuery q;
|
||||
q.SetColumnSpec("ROWID");
|
||||
q.SetOrderBy("ROWID DESC");
|
||||
q.SetIncludeUnavailable(true);
|
||||
q.SetLimit(1);
|
||||
|
||||
if (!backend_->ExecQuery(&q) || !q.Next()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
const int largest_rowid = q.Value(0).toInt();
|
||||
|
||||
for (int attempt=0 ; attempt<10 ; ++attempt) {
|
||||
LibraryQuery q;
|
||||
q.SetColumnSpec("artist, album");
|
||||
q.SetIncludeUnavailable(true);
|
||||
q.AddWhere("ROWID", qrand() % largest_rowid);
|
||||
q.SetLimit(1);
|
||||
|
||||
if (!backend_->ExecQuery(&q) || !q.Next()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QString artist = q.Value(0).toString();
|
||||
const QString album = q.Value(1).toString();
|
||||
|
||||
if (!artist.isEmpty() && !album.isEmpty())
|
||||
return (qrand() % 2 == 0) ? artist : album;
|
||||
else if (!artist.isEmpty())
|
||||
return artist;
|
||||
else if (!album.isEmpty())
|
||||
return album;
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ public:
|
||||
|
||||
ResultList Search(int id, const QString& query);
|
||||
void LoadTracksAsync(int id, const Result& result);
|
||||
QString GetSuggestion();
|
||||
|
||||
private:
|
||||
LibraryBackendInterface* backend_;
|
||||
|
@ -86,7 +86,11 @@ public:
|
||||
// Indicates this provider has a config dialog that can be shown by calling
|
||||
// CanShowConfig. If this is not set then the button will be greyed out
|
||||
// in the GUI.
|
||||
CanShowConfig = 0x10
|
||||
CanShowConfig = 0x10,
|
||||
|
||||
// This provider can provide some example search strings to display in the
|
||||
// UI.
|
||||
CanGiveSuggestions = 0x20
|
||||
};
|
||||
Q_DECLARE_FLAGS(Hints, Hint)
|
||||
|
||||
@ -100,6 +104,7 @@ public:
|
||||
bool art_is_probably_remote() const { return hints() & ArtIsProbablyRemote; }
|
||||
bool art_is_in_song_metadata() const { return hints() & ArtIsInSongMetadata; }
|
||||
bool can_show_config() const { return hints() & CanShowConfig; }
|
||||
bool can_give_suggestions() const { return hints() & CanGiveSuggestions; }
|
||||
|
||||
// Starts a search. Must emit ResultsAvailable zero or more times and then
|
||||
// SearchFinished exactly once, using this ID.
|
||||
@ -113,6 +118,11 @@ public:
|
||||
// ResultsAvailable. Must emit TracksLoaded exactly once with this ID.
|
||||
virtual void LoadTracksAsync(int id, const Result& result);
|
||||
|
||||
// Returns an example search string to display in the UI. The provider should
|
||||
// pick one of its items at random. Remember to set the CanGiveSuggestions
|
||||
// hint.
|
||||
virtual QString GetSuggestion() { return QString(); }
|
||||
|
||||
// If provider needs user login to search and play songs, this method should
|
||||
// be reimplemented
|
||||
virtual bool IsLoggedIn() { return true; }
|
||||
|
@ -124,3 +124,18 @@ void SimpleSearchProvider::SetItems(const ItemList& items) {
|
||||
QMutexLocker l(&items_mutex_);
|
||||
items_ = items;
|
||||
}
|
||||
|
||||
QString SimpleSearchProvider::GetSuggestion() {
|
||||
QMutexLocker l(&items_mutex_);
|
||||
|
||||
if (items_.isEmpty())
|
||||
return QString();
|
||||
|
||||
for (int attempt=0 ; attempt<10 ; ++attempt) {
|
||||
const Item& item = items_[qrand() % items_.count()];
|
||||
if (!item.keyword_.isEmpty())
|
||||
return item.keyword_;
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ public:
|
||||
|
||||
// SearchProvider
|
||||
void LoadTracksAsync(int id, const Result& result);
|
||||
QString GetSuggestion();
|
||||
|
||||
protected slots:
|
||||
// Calls RecreateItems now if the user has done a global search with this
|
||||
|
@ -22,7 +22,7 @@ SomaFMSearchProvider::SomaFMSearchProvider(SomaFMService* service, QObject* pare
|
||||
: SimpleSearchProvider(parent),
|
||||
service_(service)
|
||||
{
|
||||
Init("SomaFM", "somafm", QIcon(":/providers/somafm.png"));
|
||||
Init("SomaFM", "somafm", QIcon(":/providers/somafm.png"), CanGiveSuggestions);
|
||||
set_result_limit(3);
|
||||
icon_ = ScaleAndPad(QImage(":/providers/somafm.png"));
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user