Swap between two models (a "back" model and a "front" model) to smooth the delays when typing a search query
This commit is contained in:
parent
4282b6b68b
commit
30803b6743
|
@ -35,10 +35,12 @@
|
|||
#include <QSettings>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStandardItemModel>
|
||||
#include <QTimer>
|
||||
|
||||
|
||||
const int GlobalSearchWidget::kMinVisibleItems = 3;
|
||||
const int GlobalSearchWidget::kMaxVisibleItems = 25;
|
||||
const int GlobalSearchWidget::kSwapModelsTimeoutMsec = 250;
|
||||
const char* GlobalSearchWidget::kSettingsGroup = "GlobalSearch";
|
||||
|
||||
|
||||
|
@ -47,12 +49,16 @@ GlobalSearchWidget::GlobalSearchWidget(QWidget* parent)
|
|||
ui_(new Ui_GlobalSearchWidget),
|
||||
engine_(NULL),
|
||||
last_id_(0),
|
||||
clear_model_on_next_result_(false),
|
||||
order_arrived_counter_(0),
|
||||
model_(new QStandardItemModel(this)),
|
||||
proxy_(new GlobalSearchSortModel(this)),
|
||||
front_model_(new QStandardItemModel(this)),
|
||||
back_model_(new QStandardItemModel(this)),
|
||||
current_model_(front_model_),
|
||||
front_proxy_(new GlobalSearchSortModel(this)),
|
||||
back_proxy_(new GlobalSearchSortModel(this)),
|
||||
current_proxy_(front_proxy_),
|
||||
view_(new QListView),
|
||||
eat_focus_out_(false),
|
||||
swap_models_timer_(new QTimer(this)),
|
||||
background_(":allthethings.png"),
|
||||
desktop_(qApp->desktop()),
|
||||
combine_identical_results_(true)
|
||||
|
@ -61,9 +67,13 @@ GlobalSearchWidget::GlobalSearchWidget(QWidget* parent)
|
|||
ReloadSettings();
|
||||
|
||||
// Set up the sorting proxy model
|
||||
proxy_->setSourceModel(model_);
|
||||
proxy_->setDynamicSortFilter(true);
|
||||
proxy_->sort(0);
|
||||
front_proxy_->setSourceModel(front_model_);
|
||||
front_proxy_->setDynamicSortFilter(true);
|
||||
front_proxy_->sort(0);
|
||||
|
||||
back_proxy_->setSourceModel(back_model_);
|
||||
back_proxy_->setDynamicSortFilter(true);
|
||||
back_proxy_->sort(0);
|
||||
|
||||
// Set up the popup
|
||||
view_->setObjectName("popup");
|
||||
|
@ -72,7 +82,7 @@ GlobalSearchWidget::GlobalSearchWidget(QWidget* parent)
|
|||
view_->setFocusProxy(ui_->search);
|
||||
view_->installEventFilter(this);
|
||||
|
||||
view_->setModel(proxy_);
|
||||
view_->setModel(front_proxy_);
|
||||
view_->setItemDelegate(new GlobalSearchItemDelegate(this));
|
||||
view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
view_->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
|
@ -105,10 +115,14 @@ GlobalSearchWidget::GlobalSearchWidget(QWidget* parent)
|
|||
StyleSheetLoader* style_loader = new StyleSheetLoader(this);
|
||||
style_loader->SetStyleSheet(this, ":globalsearch.css");
|
||||
|
||||
swap_models_timer_->setSingleShot(true);
|
||||
swap_models_timer_->setInterval(kSwapModelsTimeoutMsec);
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
GlobalSearchWidget::~GlobalSearchWidget() {
|
||||
|
@ -123,8 +137,6 @@ void GlobalSearchWidget::Init(GlobalSearch* engine) {
|
|||
connect(engine_, SIGNAL(ResultsAvailable(int,SearchProvider::ResultList)),
|
||||
SLOT(AddResults(int,SearchProvider::ResultList)),
|
||||
Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(SearchFinished(int)), SLOT(SearchFinished(int)),
|
||||
Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(ArtLoaded(int,QPixmap)), SLOT(ArtLoaded(int,QPixmap)),
|
||||
Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(TracksLoaded(int,MimeData*)), SLOT(TracksLoaded(int,MimeData*)),
|
||||
|
@ -180,31 +192,29 @@ void GlobalSearchWidget::TextEdited(const QString& text) {
|
|||
const QString trimmed_text = text.trimmed();
|
||||
|
||||
if (trimmed_text.length() < 3) {
|
||||
Reset();
|
||||
RepositionPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
clear_model_on_next_result_ = true;
|
||||
// Add results to the back model, switch models after some delay.
|
||||
back_model_->clear();
|
||||
current_model_ = back_model_;
|
||||
current_proxy_ = back_proxy_;
|
||||
order_arrived_counter_ = 0;
|
||||
swap_models_timer_->start();
|
||||
|
||||
// Cancel the last search (if any) and start the new one.
|
||||
engine_->CancelSearch(last_id_);
|
||||
last_id_ = engine_->SearchAsync(trimmed_text);
|
||||
}
|
||||
|
||||
void GlobalSearchWidget::Reset() {
|
||||
model_->clear();
|
||||
void GlobalSearchWidget::SwapModels() {
|
||||
art_requests_.clear();
|
||||
order_arrived_counter_ = 0;
|
||||
}
|
||||
|
||||
void GlobalSearchWidget::SearchFinished(int id) {
|
||||
if (id != last_id_)
|
||||
return;
|
||||
|
||||
if (clear_model_on_next_result_) {
|
||||
Reset();
|
||||
clear_model_on_next_result_ = false;
|
||||
}
|
||||
qSwap(front_model_, back_model_);
|
||||
qSwap(front_proxy_, back_proxy_);
|
||||
|
||||
view_->setModel(front_proxy_);
|
||||
RepositionPopup();
|
||||
}
|
||||
|
||||
|
@ -212,11 +222,6 @@ void GlobalSearchWidget::AddResults(int id, const SearchProvider::ResultList& re
|
|||
if (id != last_id_)
|
||||
return;
|
||||
|
||||
if (clear_model_on_next_result_) {
|
||||
Reset();
|
||||
clear_model_on_next_result_ = false;
|
||||
}
|
||||
|
||||
foreach (const SearchProvider::Result& result, results) {
|
||||
QStandardItem* item = new QStandardItem;
|
||||
item->setData(QVariant::fromValue(result), Role_PrimaryResult);
|
||||
|
@ -228,14 +233,14 @@ void GlobalSearchWidget::AddResults(int id, const SearchProvider::ResultList& re
|
|||
item->setData(pixmap, Qt::DecorationRole);
|
||||
}
|
||||
|
||||
model_->appendRow(item);
|
||||
current_model_->appendRow(item);
|
||||
|
||||
if (combine_identical_results_) {
|
||||
// Maybe we can combine this result with an identical result from another
|
||||
// provider. We can use the sorted model to narrow the scope of the
|
||||
// search a bit - look at the result after the current one, then all the
|
||||
// results before.
|
||||
QModelIndex my_proxy_index = proxy_->mapFromSource(item->index());
|
||||
QModelIndex my_proxy_index = current_proxy_->mapFromSource(item->index());
|
||||
QModelIndexList candidates;
|
||||
candidates << my_proxy_index.sibling(my_proxy_index.row() + 1, 0);
|
||||
|
||||
|
@ -274,13 +279,13 @@ void GlobalSearchWidget::AddResults(int id, const SearchProvider::ResultList& re
|
|||
}
|
||||
|
||||
void GlobalSearchWidget::RepositionPopup() {
|
||||
if (model_->rowCount() == 0) {
|
||||
if (front_model_->rowCount() == 0) {
|
||||
HidePopup();
|
||||
return;
|
||||
}
|
||||
|
||||
int h = view_->sizeHintForRow(0) * float(0.5 +
|
||||
qBound(kMinVisibleItems, model_->rowCount(), kMaxVisibleItems));
|
||||
qBound(kMinVisibleItems, front_model_->rowCount(), kMaxVisibleItems));
|
||||
int w = ui_->search->width();
|
||||
|
||||
const QPoint pos = ui_->search->mapToGlobal(ui_->search->rect().bottomLeft());
|
||||
|
@ -364,7 +369,7 @@ bool GlobalSearchWidget::EventFilterPopup(QObject*, QEvent* e) {
|
|||
|
||||
case Qt::Key_Up:
|
||||
if (!cur_index.isValid()) {
|
||||
view_->setCurrentIndex(proxy_->index(proxy_->rowCount() - 1, 0));
|
||||
view_->setCurrentIndex(front_proxy_->index(front_proxy_->rowCount() - 1, 0));
|
||||
return true;
|
||||
} else if (cur_index.row() == 0) {
|
||||
return true;
|
||||
|
@ -373,9 +378,9 @@ bool GlobalSearchWidget::EventFilterPopup(QObject*, QEvent* e) {
|
|||
|
||||
case Qt::Key_Down:
|
||||
if (!cur_index.isValid()) {
|
||||
view_->setCurrentIndex(proxy_->index(0, 0));
|
||||
view_->setCurrentIndex(front_proxy_->index(0, 0));
|
||||
return true;
|
||||
} else if (cur_index.row() == proxy_->rowCount() - 1) {
|
||||
} else if (cur_index.row() == front_proxy_->rowCount() - 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -456,10 +461,12 @@ void GlobalSearchWidget::LazyLoadArt(const QModelIndex& proxy_index) {
|
|||
if (!proxy_index.isValid() || proxy_index.data(Role_LazyLoadingArt).isValid()) {
|
||||
return;
|
||||
}
|
||||
if (proxy_index.model() != front_proxy_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QModelIndex source_index = proxy_->mapToSource(proxy_index);
|
||||
|
||||
model_->itemFromIndex(source_index)->setData(true, Role_LazyLoadingArt);
|
||||
const QModelIndex source_index = front_proxy_->mapToSource(proxy_index);
|
||||
front_model_->itemFromIndex(source_index)->setData(true, Role_LazyLoadingArt);
|
||||
|
||||
const SearchProvider::Result result =
|
||||
source_index.data(Role_PrimaryResult).value<SearchProvider::Result>();
|
||||
|
@ -473,7 +480,7 @@ void GlobalSearchWidget::ArtLoaded(int id, const QPixmap& pixmap) {
|
|||
return;
|
||||
QModelIndex index = art_requests_.take(id);
|
||||
|
||||
model_->itemFromIndex(index)->setData(pixmap, Qt::DecorationRole);
|
||||
front_model_->itemFromIndex(index)->setData(pixmap, Qt::DecorationRole);
|
||||
}
|
||||
|
||||
void GlobalSearchWidget::ResultDoubleClicked() {
|
||||
|
@ -503,7 +510,7 @@ void GlobalSearchWidget::ReplaceAndPlayCurrent() {
|
|||
void GlobalSearchWidget::LoadTracks(QAction* trigger) {
|
||||
QModelIndex index = view_->currentIndex();
|
||||
if (!index.isValid())
|
||||
index = proxy_->index(0, 0);
|
||||
index = front_proxy_->index(0, 0);
|
||||
|
||||
if (!index.isValid())
|
||||
return;
|
||||
|
@ -598,8 +605,8 @@ GlobalSearchWidget::CombineAction GlobalSearchWidget::CanCombineResults(
|
|||
}
|
||||
|
||||
void GlobalSearchWidget::CombineResults(const QModelIndex& superior, const QModelIndex& inferior) {
|
||||
QStandardItem* superior_item = model_->itemFromIndex(proxy_->mapToSource(superior));
|
||||
QStandardItem* inferior_item = model_->itemFromIndex(proxy_->mapToSource(inferior));
|
||||
QStandardItem* superior_item = current_model_->itemFromIndex(current_proxy_->mapToSource(superior));
|
||||
QStandardItem* inferior_item = current_model_->itemFromIndex(current_proxy_->mapToSource(inferior));
|
||||
|
||||
SearchProvider::ResultList superior_results =
|
||||
superior_item->data(Role_AllResults).value<SearchProvider::ResultList>();
|
||||
|
@ -609,7 +616,7 @@ void GlobalSearchWidget::CombineResults(const QModelIndex& superior, const QMode
|
|||
superior_results.append(inferior_results);
|
||||
superior_item->setData(QVariant::fromValue(superior_results), Role_AllResults);
|
||||
|
||||
model_->invisibleRootItem()->removeRow(inferior_item->row());
|
||||
current_model_->invisibleRootItem()->removeRow(inferior_item->row());
|
||||
}
|
||||
|
||||
void GlobalSearchWidget::HidePopup() {
|
||||
|
|
|
@ -45,6 +45,7 @@ public:
|
|||
|
||||
static const int kMinVisibleItems;
|
||||
static const int kMaxVisibleItems;
|
||||
static const int kSwapModelsTimeoutMsec;
|
||||
static const char* kSettingsGroup;
|
||||
|
||||
enum Role {
|
||||
|
@ -74,7 +75,6 @@ protected:
|
|||
|
||||
private slots:
|
||||
void TextEdited(const QString& text);
|
||||
void SearchFinished(int id);
|
||||
void AddResults(int id, const SearchProvider::ResultList& results);
|
||||
|
||||
void ArtLoaded(int id, const QPixmap& pixmap);
|
||||
|
@ -91,6 +91,8 @@ private slots:
|
|||
void HidePopup();
|
||||
void UpdateTooltip();
|
||||
|
||||
void SwapModels();
|
||||
|
||||
private:
|
||||
// Return values from CanCombineResults
|
||||
enum CombineAction {
|
||||
|
@ -99,7 +101,6 @@ private:
|
|||
RightPreferred // The two results can be combined - the right one is better
|
||||
};
|
||||
|
||||
void Reset();
|
||||
void RepositionPopup();
|
||||
CombineAction CanCombineResults(const QModelIndex& left, const QModelIndex& right) const;
|
||||
void CombineResults(const QModelIndex& superior, const QModelIndex& inferior);
|
||||
|
@ -114,17 +115,28 @@ private:
|
|||
|
||||
GlobalSearch* engine_;
|
||||
int last_id_;
|
||||
bool clear_model_on_next_result_;
|
||||
int order_arrived_counter_;
|
||||
|
||||
QMap<int, QModelIndex> art_requests_;
|
||||
QMap<int, QAction*> track_requests_;
|
||||
|
||||
QStandardItemModel* model_;
|
||||
QSortFilterProxyModel* proxy_;
|
||||
// Like graphics APIs have a front buffer and a back buffer, there's a front
|
||||
// model and a back model - the front model is the one that's shown in the
|
||||
// UI and the back model is the one that lies in wait. current_model_ will
|
||||
// point to either the front or the back model.
|
||||
QStandardItemModel* front_model_;
|
||||
QStandardItemModel* back_model_;
|
||||
QStandardItemModel* current_model_;
|
||||
|
||||
QSortFilterProxyModel* front_proxy_;
|
||||
QSortFilterProxyModel* back_proxy_;
|
||||
QSortFilterProxyModel* current_proxy_;
|
||||
|
||||
QListView* view_;
|
||||
bool eat_focus_out_;
|
||||
|
||||
QTimer* swap_models_timer_;
|
||||
|
||||
QPixmap background_;
|
||||
QPixmap background_scaled_;
|
||||
|
||||
|
|
Loading…
Reference in New Issue