Show some suggested queries in the global search widget

This commit is contained in:
David Sansome 2011-11-06 16:29:09 +00:00
parent f79d38210c
commit 63f39d8dec
13 changed files with 140 additions and 8 deletions

View File

@ -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");

View File

@ -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;
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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

View File

@ -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>

View File

@ -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");

View File

@ -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();
}

View File

@ -30,6 +30,7 @@ public:
ResultList Search(int id, const QString& query);
void LoadTracksAsync(int id, const Result& result);
QString GetSuggestion();
private:
LibraryBackendInterface* backend_;

View File

@ -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; }

View File

@ -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();
}

View File

@ -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

View File

@ -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"));