Started work on a global search for library, with album art.
The idea is that there's a single place to search for music, and it doesn't matter where it is, Clementine will find something playable.
This commit is contained in:
parent
2cbe90c2e5
commit
9370657b50
BIN
data/allthethings.png
Normal file
BIN
data/allthethings.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
@ -343,5 +343,6 @@
|
||||
<file>providers/digitallyimported.png</file>
|
||||
<file>providers/skyfm.png</file>
|
||||
<file>providers/digitallyimported-32.png</file>
|
||||
<file>allthethings.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
@ -115,6 +115,11 @@ set(SOURCES
|
||||
engines/gstenginepipeline.cpp
|
||||
engines/gstelementdeleter.cpp
|
||||
|
||||
globalsearch/globalsearch.cpp
|
||||
globalsearch/globalsearchwidget.cpp
|
||||
globalsearch/librarysearchprovider.cpp
|
||||
globalsearch/searchprovider.cpp
|
||||
|
||||
internet/digitallyimportedservice.cpp
|
||||
internet/digitallyimportedservicebase.cpp
|
||||
internet/digitallyimportedsettingspage.cpp
|
||||
@ -347,6 +352,11 @@ set(HEADERS
|
||||
engines/gstenginepipeline.h
|
||||
engines/gstelementdeleter.h
|
||||
|
||||
globalsearch/librarysearchprovider.h
|
||||
globalsearch/globalsearch.h
|
||||
globalsearch/globalsearchwidget.h
|
||||
globalsearch/searchprovider.h
|
||||
|
||||
internet/digitallyimportedservicebase.h
|
||||
internet/digitallyimportedsettingspage.h
|
||||
internet/icecastbackend.h
|
||||
@ -508,6 +518,8 @@ set(UI
|
||||
|
||||
devices/deviceproperties.ui
|
||||
|
||||
globalsearch/globalsearchwidget.ui
|
||||
|
||||
internet/digitallyimportedsettingspage.ui
|
||||
internet/icecastfilterwidget.ui
|
||||
internet/internetviewcontainer.ui
|
||||
|
91
src/globalsearch/globalsearch.cpp
Normal file
91
src/globalsearch/globalsearch.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "librarysearchprovider.h"
|
||||
#include "globalsearch.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
GlobalSearch::GlobalSearch(QObject* parent)
|
||||
: QObject(parent),
|
||||
next_id_(1)
|
||||
{
|
||||
}
|
||||
|
||||
void GlobalSearch::AddProvider(SearchProvider* provider) {
|
||||
connect(provider, SIGNAL(ResultsAvailable(int,SearchProvider::ResultList)),
|
||||
SLOT(ResultsAvailableSlot(int,SearchProvider::ResultList)));
|
||||
connect(provider, SIGNAL(SearchFinished(int)),
|
||||
SLOT(SearchFinishedSlot(int)));
|
||||
connect(provider, SIGNAL(ArtLoaded(int,QImage)), SIGNAL(ArtLoaded(int,QImage)));
|
||||
connect(provider, SIGNAL(destroyed(QObject*)),
|
||||
SLOT(ProviderDestroyedSlot(QObject*)));
|
||||
|
||||
providers_ << provider;
|
||||
}
|
||||
|
||||
int GlobalSearch::SearchAsync(const QString& query) {
|
||||
const int id = next_id_ ++;
|
||||
|
||||
pending_search_providers_[id] = providers_.count();
|
||||
foreach (SearchProvider* provider, providers_) {
|
||||
provider->SearchAsync(id, query);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void GlobalSearch::ResultsAvailableSlot(int id, const SearchProvider::ResultList& results) {
|
||||
if (!results.isEmpty())
|
||||
emit ResultsAvailable(id, results);
|
||||
}
|
||||
|
||||
void GlobalSearch::SearchFinishedSlot(int id) {
|
||||
if (!pending_search_providers_.contains(id))
|
||||
return;
|
||||
|
||||
SearchProvider* provider = static_cast<SearchProvider*>(sender());
|
||||
const int remaining = --pending_search_providers_[id];
|
||||
|
||||
emit ProviderSearchFinished(id, provider);
|
||||
if (remaining == 0) {
|
||||
emit SearchFinished(id);
|
||||
pending_search_providers_.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalSearch::ProviderDestroyedSlot(QObject* object) {
|
||||
SearchProvider* provider = static_cast<SearchProvider*>(object);
|
||||
if (!providers_.contains(provider))
|
||||
return;
|
||||
|
||||
providers_.removeAll(provider);
|
||||
emit ProviderDestroyed(provider);
|
||||
|
||||
// We have to abort any pending searches since we can't tell whether they
|
||||
// were on this provider.
|
||||
foreach (int id, pending_search_providers_.keys()) {
|
||||
emit SearchFinished(id);
|
||||
}
|
||||
pending_search_providers_.clear();
|
||||
}
|
||||
|
||||
int GlobalSearch::LoadArtAsync(const SearchProvider::Result& result) {
|
||||
const int id = next_id_ ++;
|
||||
result.provider_->LoadArtAsync(id, result);
|
||||
return id;
|
||||
}
|
||||
|
59
src/globalsearch/globalsearch.h
Normal file
59
src/globalsearch/globalsearch.h
Normal file
@ -0,0 +1,59 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef GLOBALSEARCH_H
|
||||
#define GLOBALSEARCH_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "searchprovider.h"
|
||||
|
||||
|
||||
class GlobalSearch : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GlobalSearch(QObject* parent = 0);
|
||||
|
||||
void AddProvider(SearchProvider* provider);
|
||||
|
||||
int SearchAsync(const QString& query);
|
||||
int LoadArtAsync(const SearchProvider::Result& result);
|
||||
|
||||
signals:
|
||||
void ResultsAvailable(int id, const SearchProvider::ResultList& results);
|
||||
void ProviderSearchFinished(int id, const SearchProvider* provider);
|
||||
void SearchFinished(int id);
|
||||
|
||||
void ArtLoaded(int id, const QImage& image);
|
||||
|
||||
void ProviderDestroyed(SearchProvider* provider);
|
||||
|
||||
private slots:
|
||||
void ResultsAvailableSlot(int id, const SearchProvider::ResultList& results);
|
||||
void SearchFinishedSlot(int id);
|
||||
|
||||
void ProviderDestroyedSlot(QObject* object);
|
||||
|
||||
private:
|
||||
QList<SearchProvider*> providers_;
|
||||
|
||||
int next_id_;
|
||||
QMap<int, int> pending_search_providers_;
|
||||
};
|
||||
|
||||
#endif // GLOBALSEARCH_H
|
452
src/globalsearch/globalsearchwidget.cpp
Normal file
452
src/globalsearch/globalsearchwidget.cpp
Normal file
@ -0,0 +1,452 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "globalsearch.h"
|
||||
#include "globalsearchwidget.h"
|
||||
#include "librarysearchprovider.h"
|
||||
#include "ui_globalsearchwidget.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/utilities.h"
|
||||
#include "widgets/stylehelper.h"
|
||||
|
||||
#include <QListView>
|
||||
#include <QPainter>
|
||||
#include <QStandardItemModel>
|
||||
|
||||
const int GlobalSearchItemDelegate::kHeight = SearchProvider::kArtHeight;
|
||||
const int GlobalSearchItemDelegate::kMargin = 1;
|
||||
const int GlobalSearchItemDelegate::kArtMargin = 6;
|
||||
const int GlobalSearchItemDelegate::kWordPadding = 6;
|
||||
const int GlobalSearchWidget::kMinVisibleItems = 3;
|
||||
const int GlobalSearchWidget::kMaxVisibleItems = 12;
|
||||
|
||||
|
||||
GlobalSearchItemDelegate::GlobalSearchItemDelegate(GlobalSearchWidget* widget)
|
||||
: QStyledItemDelegate(widget),
|
||||
widget_(widget)
|
||||
{
|
||||
no_cover_ = ScaleAndPad(QImage(":nocover.png"));
|
||||
}
|
||||
|
||||
QPixmap GlobalSearchItemDelegate::ScaleAndPad(const QImage& image) {
|
||||
if (image.isNull())
|
||||
return QPixmap();
|
||||
|
||||
if (image.size() == QSize(kHeight, kHeight))
|
||||
return QPixmap::fromImage(image);
|
||||
|
||||
// Scale the image down
|
||||
QImage copy;
|
||||
copy = image.scaled(QSize(kHeight, kHeight),
|
||||
Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
// Pad the image to kHeight x kHeight
|
||||
QImage padded_image(kHeight, kHeight, QImage::Format_ARGB32);
|
||||
padded_image.fill(0);
|
||||
|
||||
QPainter p(&padded_image);
|
||||
p.drawImage((kHeight - copy.width()) / 2, (kHeight - copy.height()) / 2,
|
||||
copy);
|
||||
p.end();
|
||||
|
||||
return QPixmap::fromImage(padded_image);
|
||||
}
|
||||
|
||||
QSize GlobalSearchItemDelegate::sizeHint(const QStyleOptionViewItem& option,
|
||||
const QModelIndex& index) const {
|
||||
QSize size = QStyledItemDelegate::sizeHint(option, index);
|
||||
size.setHeight(kHeight + kMargin);
|
||||
return size;
|
||||
}
|
||||
|
||||
void GlobalSearchItemDelegate::DrawAndShrink(QPainter* p, QRect* rect,
|
||||
const QString& text) const {
|
||||
QRect br;
|
||||
p->drawText(*rect, Qt::TextSingleLine | Qt::AlignVCenter, text, &br);
|
||||
rect->setLeft(br.right() + kWordPadding);
|
||||
}
|
||||
|
||||
void GlobalSearchItemDelegate::paint(QPainter* p,
|
||||
const QStyleOptionViewItem& option,
|
||||
const QModelIndex& index) const {
|
||||
const SearchProvider::Result result =
|
||||
index.data(GlobalSearchWidget::Role_Result).value<SearchProvider::Result>();
|
||||
const Song& m = result.metadata_;
|
||||
|
||||
widget_->LazyLoadArt(index);
|
||||
|
||||
QFont bold_font = option.font;
|
||||
bold_font.setBold(true);
|
||||
|
||||
QColor pen = option.palette.color(QPalette::Text);
|
||||
QColor light_pen = pen;
|
||||
pen.setAlpha(200);
|
||||
light_pen.setAlpha(128);
|
||||
|
||||
// Draw the background
|
||||
const QStyleOptionViewItemV3* vopt = qstyleoption_cast<const QStyleOptionViewItemV3*>(&option);
|
||||
const QWidget* widget = vopt->widget;
|
||||
QStyle* style = widget->style() ? widget->style() : QApplication::style();
|
||||
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, p, widget);
|
||||
|
||||
// Draw the album art. This will already be the correct size.
|
||||
const QRect rect = option.rect;
|
||||
const QRect art_rect(rect.left() + kMargin, rect.top(), kHeight, kHeight);
|
||||
|
||||
QPixmap art = index.data(Qt::DecorationRole).value<QPixmap>();
|
||||
if (art.isNull())
|
||||
art = no_cover_;
|
||||
|
||||
p->drawPixmap(art_rect, art);
|
||||
|
||||
// Position text
|
||||
QRect text_rect(art_rect.right() + kArtMargin, art_rect.top(),
|
||||
rect.right() - art_rect.right() - kArtMargin, kHeight);
|
||||
QRect text_rect_1(text_rect.adjusted(0, 0, 0, -kHeight/2));
|
||||
QRect text_rect_2(text_rect.adjusted(0, kHeight/2, 0, 0));
|
||||
|
||||
// The text we draw depends on the type of result.
|
||||
switch (result.type_) {
|
||||
case SearchProvider::Result::Type_Track: {
|
||||
// Line 1 is Title
|
||||
p->setFont(bold_font);
|
||||
|
||||
// Title
|
||||
p->setPen(pen);
|
||||
DrawAndShrink(p, &text_rect_1, m.title());
|
||||
|
||||
// Line 2 is Artist - Album
|
||||
p->setFont(option.font);
|
||||
|
||||
// Artist
|
||||
p->setPen(pen);
|
||||
if (!m.artist().isEmpty()) {
|
||||
DrawAndShrink(p, &text_rect_2, m.artist());
|
||||
} else if (!m.albumartist().isEmpty()) {
|
||||
DrawAndShrink(p, &text_rect_2, m.albumartist());
|
||||
}
|
||||
|
||||
if (!m.album().isEmpty()) {
|
||||
// Dash
|
||||
p->setPen(light_pen);
|
||||
DrawAndShrink(p, &text_rect_2, " - ");
|
||||
|
||||
// Album
|
||||
p->setPen(pen);
|
||||
DrawAndShrink(p, &text_rect_2, m.album());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SearchProvider::Result::Type_Album: {
|
||||
// Line 1 is Artist - Album
|
||||
p->setFont(bold_font);
|
||||
|
||||
// Artist
|
||||
p->setPen(pen);
|
||||
if (!m.albumartist().isEmpty())
|
||||
DrawAndShrink(p, &text_rect_1, m.albumartist());
|
||||
else if (m.is_compilation())
|
||||
DrawAndShrink(p, &text_rect_1, tr("Various Artists"));
|
||||
else if (!m.artist().isEmpty())
|
||||
DrawAndShrink(p, &text_rect_1, m.artist());
|
||||
else
|
||||
DrawAndShrink(p, &text_rect_1, tr("Unknown"));
|
||||
|
||||
// Dash
|
||||
p->setPen(light_pen);
|
||||
DrawAndShrink(p, &text_rect_1, " - ");
|
||||
|
||||
// Album
|
||||
p->setPen(pen);
|
||||
if (m.album().isEmpty())
|
||||
DrawAndShrink(p, &text_rect_1, tr("Unknown"));
|
||||
else
|
||||
DrawAndShrink(p, &text_rect_1, m.album());
|
||||
|
||||
// Line 2 is <n> tracks
|
||||
p->setFont(option.font);
|
||||
|
||||
p->setPen(pen);
|
||||
DrawAndShrink(p, &text_rect_2, QString::number(result.album_size_));
|
||||
|
||||
p->setPen(light_pen);
|
||||
DrawAndShrink(p, &text_rect_2, tr(result.album_size_ == 1 ? "track" : "tracks"));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
GlobalSearchWidget::GlobalSearchWidget(QWidget* parent)
|
||||
: QWidget(parent),
|
||||
ui_(new Ui_GlobalSearchWidget),
|
||||
engine_(new GlobalSearch(this)),
|
||||
last_id_(0),
|
||||
clear_model_on_next_result_(false),
|
||||
model_(new QStandardItemModel(this)),
|
||||
view_(new QListView),
|
||||
eat_focus_out_(false),
|
||||
background_(":allthethings.png")
|
||||
{
|
||||
ui_->setupUi(this);
|
||||
|
||||
view_->setWindowFlags(Qt::Popup);
|
||||
view_->setFocusPolicy(Qt::NoFocus);
|
||||
view_->setFocusProxy(ui_->search);
|
||||
view_->installEventFilter(this);
|
||||
|
||||
view_->setModel(model_);
|
||||
view_->setItemDelegate(new GlobalSearchItemDelegate(this));
|
||||
view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
|
||||
connect(ui_->search, SIGNAL(textEdited(QString)), SLOT(TextEdited(QString)));
|
||||
connect(engine_, SIGNAL(ResultsAvailable(int,SearchProvider::ResultList)),
|
||||
SLOT(AddResults(int,SearchProvider::ResultList)));
|
||||
connect(engine_, SIGNAL(ArtLoaded(int,QImage)), SLOT(ArtLoaded(int,QImage)));
|
||||
}
|
||||
|
||||
GlobalSearchWidget::~GlobalSearchWidget() {
|
||||
delete ui_;
|
||||
}
|
||||
|
||||
void GlobalSearchWidget::Init(LibraryBackendInterface* library) {
|
||||
engine_->AddProvider(new LibrarySearchProvider(
|
||||
library, tr("Library"), IconLoader::Load("folder-sound"), engine_));
|
||||
|
||||
// The style helper's base color doesn't get initialised until after the
|
||||
// constructor.
|
||||
QPalette view_palette = view_->palette();
|
||||
view_palette.setColor(QPalette::Text, Qt::white);
|
||||
view_palette.setColor(QPalette::Base, Utils::StyleHelper::shadowColor().darker(109));
|
||||
|
||||
QFont view_font = view_->font();
|
||||
view_font.setPointSizeF(Utils::StyleHelper::sidebarFontSize());
|
||||
|
||||
view_->setFont(view_font);
|
||||
view_->setPalette(view_palette);
|
||||
}
|
||||
|
||||
void GlobalSearchWidget::resizeEvent(QResizeEvent* e) {
|
||||
background_scaled_ = background_.scaled(size(), Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
|
||||
QWidget::resizeEvent(e);
|
||||
}
|
||||
|
||||
void GlobalSearchWidget::paintEvent(QPaintEvent* e) {
|
||||
QPainter p(this);
|
||||
|
||||
QRect total_rect = rect().adjusted(0, 0, 1, 0);
|
||||
total_rect = style()->visualRect(layoutDirection(), geometry(), total_rect);
|
||||
Utils::StyleHelper::verticalGradient(&p, total_rect, total_rect);
|
||||
|
||||
QRect background_rect = background_scaled_.rect();
|
||||
background_rect.moveLeft(total_rect.right() - background_rect.width());
|
||||
background_rect.moveTop(total_rect.top());
|
||||
|
||||
p.setOpacity(0.5);
|
||||
p.drawPixmap(background_rect, background_scaled_);
|
||||
p.setOpacity(1.0);
|
||||
|
||||
p.setPen(Utils::StyleHelper::borderColor());
|
||||
p.drawLine(total_rect.topRight(), total_rect.bottomRight());
|
||||
|
||||
QColor light = Utils::StyleHelper::sidebarHighlight();
|
||||
p.setPen(light);
|
||||
p.drawLine(total_rect.bottomLeft(), total_rect.bottomRight());
|
||||
}
|
||||
|
||||
void GlobalSearchWidget::TextEdited(const QString& text) {
|
||||
clear_model_on_next_result_ = true;
|
||||
last_id_ = engine_->SearchAsync(text);
|
||||
}
|
||||
|
||||
void GlobalSearchWidget::AddResults(int id, const SearchProvider::ResultList& results) {
|
||||
if (id != last_id_)
|
||||
return;
|
||||
|
||||
if (clear_model_on_next_result_) {
|
||||
model_->clear();
|
||||
art_requests_.clear();
|
||||
clear_model_on_next_result_ = false;
|
||||
}
|
||||
|
||||
foreach (const SearchProvider::Result& result, results) {
|
||||
QStandardItem* item = new QStandardItem;
|
||||
item->setData(QVariant::fromValue(result), Role_Result);
|
||||
|
||||
model_->appendRow(item);
|
||||
}
|
||||
|
||||
RepositionPopup();
|
||||
}
|
||||
|
||||
void GlobalSearchWidget::RepositionPopup() {
|
||||
if (model_->rowCount() == 0) {
|
||||
view_->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
int h = view_->sizeHintForRow(0) * float(0.5 +
|
||||
qBound(kMinVisibleItems, model_->rowCount(), kMaxVisibleItems));
|
||||
int w = ui_->search->width();
|
||||
|
||||
QPoint pos = ui_->search->mapToGlobal(ui_->search->rect().bottomLeft());
|
||||
|
||||
view_->setGeometry(QRect(pos, QSize(w, h)));
|
||||
|
||||
if (!view_->isVisible())
|
||||
view_->show();
|
||||
}
|
||||
|
||||
bool GlobalSearchWidget::eventFilter(QObject* o, QEvent* e) {
|
||||
// Most of this is borrowed from QCompleter::eventFilter
|
||||
|
||||
if (eat_focus_out_ && o == ui_->search && e->type() == QEvent::FocusOut) {
|
||||
if (view_->isVisible())
|
||||
return true;
|
||||
}
|
||||
|
||||
if (o != view_)
|
||||
return QWidget::eventFilter(o, e);
|
||||
|
||||
switch (e->type()) {
|
||||
case QEvent::KeyPress: {
|
||||
QKeyEvent* ke = static_cast<QKeyEvent*>(e);
|
||||
|
||||
QModelIndex cur_index = view_->currentIndex();
|
||||
const int key = ke->key();
|
||||
|
||||
// Handle popup navigation keys. These are hardcoded because up/down might make the
|
||||
// widget do something else (lineedit cursor moves to home/end on mac, for instance)
|
||||
switch (key) {
|
||||
case Qt::Key_End:
|
||||
case Qt::Key_Home:
|
||||
if (ke->modifiers() & Qt::ControlModifier)
|
||||
return false;
|
||||
break;
|
||||
|
||||
case Qt::Key_Up:
|
||||
if (!cur_index.isValid()) {
|
||||
view_->setCurrentIndex(model_->index(model_->rowCount() - 1, 0));
|
||||
return true;
|
||||
} else if (cur_index.row() == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
case Qt::Key_Down:
|
||||
if (!cur_index.isValid()) {
|
||||
view_->setCurrentIndex(model_->index(0, 0));
|
||||
return true;
|
||||
} else if (cur_index.row() == model_->rowCount() - 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
case Qt::Key_PageUp:
|
||||
case Qt::Key_PageDown:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send the event to the widget. If the widget accepted the event, do nothing
|
||||
// If the widget did not accept the event, provide a default implementation
|
||||
eat_focus_out_ = false;
|
||||
(static_cast<QObject *>(ui_->search))->event(ke);
|
||||
eat_focus_out_ = true;
|
||||
|
||||
if (e->isAccepted() || !view_->isVisible()) {
|
||||
// widget lost focus, hide the popup
|
||||
if (!ui_->search->hasFocus())
|
||||
view_->hide();
|
||||
if (e->isAccepted())
|
||||
return true;
|
||||
}
|
||||
|
||||
// default implementation for keys not handled by the widget when popup is open
|
||||
switch (key) {
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Tab:
|
||||
view_->hide();
|
||||
// TODO: complete
|
||||
break;
|
||||
|
||||
case Qt::Key_F4:
|
||||
if (ke->modifiers() & Qt::AltModifier)
|
||||
view_->hide();
|
||||
break;
|
||||
|
||||
case Qt::Key_Backtab:
|
||||
case Qt::Key_Escape:
|
||||
view_->hide();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::MouseButtonPress:
|
||||
if (!view_->underMouse()) {
|
||||
view_->hide();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
case QEvent::InputMethod:
|
||||
case QEvent::ShortcutOverride:
|
||||
QApplication::sendEvent(ui_->search, e);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GlobalSearchWidget::LazyLoadArt(const QModelIndex& index) {
|
||||
if (!index.isValid() || index.data(Role_LazyLoadingArt).isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
model_->itemFromIndex(index)->setData(true, Role_LazyLoadingArt);
|
||||
|
||||
const SearchProvider::Result result =
|
||||
index.data(Role_Result).value<SearchProvider::Result>();
|
||||
|
||||
int id = engine_->LoadArtAsync(result);
|
||||
art_requests_[id] = index;
|
||||
}
|
||||
|
||||
void GlobalSearchWidget::ArtLoaded(int id, const QImage& image) {
|
||||
if (!art_requests_.contains(id))
|
||||
return;
|
||||
QModelIndex index = art_requests_.take(id);
|
||||
|
||||
model_->itemFromIndex(index)->setData(
|
||||
GlobalSearchItemDelegate::ScaleAndPad(image), Qt::DecorationRole);
|
||||
}
|
||||
|
112
src/globalsearch/globalsearchwidget.h
Normal file
112
src/globalsearch/globalsearchwidget.h
Normal file
@ -0,0 +1,112 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef GLOBALSEARCHWIDGET_H
|
||||
#define GLOBALSEARCHWIDGET_H
|
||||
|
||||
#include "searchprovider.h"
|
||||
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QWidget>
|
||||
|
||||
class GlobalSearch;
|
||||
class GlobalSearchWidget;
|
||||
class LibraryBackendInterface;
|
||||
class Ui_GlobalSearchWidget;
|
||||
|
||||
class QListView;
|
||||
class QStandardItemModel;
|
||||
|
||||
|
||||
class GlobalSearchItemDelegate : public QStyledItemDelegate {
|
||||
public:
|
||||
GlobalSearchItemDelegate(GlobalSearchWidget* widget);
|
||||
|
||||
static const int kHeight;
|
||||
static const int kMargin;
|
||||
static const int kArtMargin;
|
||||
static const int kWordPadding;
|
||||
|
||||
static QPixmap ScaleAndPad(const QImage& image);
|
||||
|
||||
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
|
||||
void paint(QPainter* painter, const QStyleOptionViewItem& option,
|
||||
const QModelIndex& index) const;
|
||||
|
||||
private:
|
||||
void DrawAndShrink(QPainter* p, QRect* rect, const QString& text) const;
|
||||
|
||||
private:
|
||||
GlobalSearchWidget* widget_;
|
||||
QPixmap no_cover_;
|
||||
};
|
||||
|
||||
|
||||
class GlobalSearchWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GlobalSearchWidget(QWidget *parent = 0);
|
||||
~GlobalSearchWidget();
|
||||
|
||||
static const int kMinVisibleItems;
|
||||
static const int kMaxVisibleItems;
|
||||
|
||||
enum Role {
|
||||
Role_Result = Qt::UserRole + 1,
|
||||
Role_LazyLoadingArt
|
||||
};
|
||||
|
||||
void Init(LibraryBackendInterface* library);
|
||||
|
||||
// Called by the delegate
|
||||
void LazyLoadArt(const QModelIndex& index);
|
||||
|
||||
// QWidget
|
||||
bool eventFilter(QObject* o, QEvent* e);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* e);
|
||||
void paintEvent(QPaintEvent* e);
|
||||
|
||||
private slots:
|
||||
void TextEdited(const QString& text);
|
||||
void AddResults(int id, const SearchProvider::ResultList& results);
|
||||
|
||||
void ArtLoaded(int id, const QImage& image);
|
||||
|
||||
private:
|
||||
void RepositionPopup();
|
||||
|
||||
private:
|
||||
Ui_GlobalSearchWidget* ui_;
|
||||
|
||||
GlobalSearch* engine_;
|
||||
int last_id_;
|
||||
bool clear_model_on_next_result_;
|
||||
|
||||
QMap<int, QModelIndex> art_requests_;
|
||||
|
||||
QStandardItemModel* model_;
|
||||
QListView* view_;
|
||||
bool eat_focus_out_;
|
||||
|
||||
QPixmap background_;
|
||||
QPixmap background_scaled_;
|
||||
};
|
||||
|
||||
#endif // GLOBALSEARCHWIDGET_H
|
119
src/globalsearch/globalsearchwidget.ui
Normal file
119
src/globalsearch/globalsearchwidget.ui
Normal file
@ -0,0 +1,119 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>GlobalSearchWidget</class>
|
||||
<widget class="QWidget" name="GlobalSearchWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>522</width>
|
||||
<height>101</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">* {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 7.5pt;
|
||||
}
|
||||
|
||||
#search {
|
||||
border: 1px groove rgba(128, 128, 128, 60%);
|
||||
background-color: rgba(0, 0, 0, 20%)
|
||||
}
|
||||
|
||||
QToolButton {
|
||||
font-weight: normal;
|
||||
font-weight: bold;
|
||||
color: rgba(255, 255, 255, 50%);
|
||||
}
|
||||
|
||||
QToolButton:hover {
|
||||
background-color: black;
|
||||
border: 2px solid rgba(255, 255, 255, 20%);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
QToolButton:pressed {
|
||||
border: 2px solid rgba(255, 255, 255, 20%);
|
||||
background-color: rgba(255, 255, 255, 10%);
|
||||
}
|
||||
|
||||
|
||||
</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Search <b>ALL THE THINGS</b> in your library, connected devices and on the Internet.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="LineEdit" name="search">
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Include:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="toolButton">
|
||||
<property name="text">
|
||||
<string>Library</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="toolButton_2">
|
||||
<property name="text">
|
||||
<string>Spotify</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="toolButton_3">
|
||||
<property name="text">
|
||||
<string>Magnatune</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>LineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>widgets/lineedit.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
116
src/globalsearch/librarysearchprovider.cpp
Normal file
116
src/globalsearch/librarysearchprovider.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "librarysearchprovider.h"
|
||||
#include "core/logging.h"
|
||||
#include "covers/albumcoverloader.h"
|
||||
#include "library/librarybackend.h"
|
||||
#include "library/libraryquery.h"
|
||||
#include "library/sqlrow.h"
|
||||
|
||||
|
||||
LibrarySearchProvider::LibrarySearchProvider(LibraryBackendInterface* backend,
|
||||
const QString& name,
|
||||
const QIcon& icon,
|
||||
QObject* parent)
|
||||
: BlockingSearchProvider(name, icon, parent),
|
||||
backend_(backend),
|
||||
cover_loader_(new BackgroundThreadImplementation<AlbumCoverLoader, AlbumCoverLoader>(this))
|
||||
{
|
||||
cover_loader_->Start(true);
|
||||
cover_loader_->Worker()->SetDesiredHeight(kArtHeight);
|
||||
cover_loader_->Worker()->SetPadOutputImage(true);
|
||||
cover_loader_->Worker()->SetScaleOutputImage(true);
|
||||
|
||||
connect(cover_loader_->Worker().get(),
|
||||
SIGNAL(ImageLoaded(quint64,QImage)),
|
||||
SLOT(AlbumArtLoaded(quint64,QImage)));
|
||||
}
|
||||
|
||||
SearchProvider::ResultList LibrarySearchProvider::Search(int id, const QString& query) {
|
||||
QueryOptions options;
|
||||
options.set_filter(query);
|
||||
|
||||
LibraryQuery q(options);
|
||||
q.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
|
||||
|
||||
if (!backend_->ExecQuery(&q)) {
|
||||
return ResultList();
|
||||
}
|
||||
|
||||
const QStringList tokens = TokenizeQuery(query);
|
||||
|
||||
QMultiMap<QString, Song> albums;
|
||||
QSet<QString> albums_with_non_track_matches;
|
||||
|
||||
ResultList ret;
|
||||
|
||||
while (q.Next()) {
|
||||
Song song;
|
||||
song.InitFromQuery(q, true);
|
||||
|
||||
QString album_key = song.album();
|
||||
if (song.is_compilation() && !song.albumartist().isEmpty()) {
|
||||
album_key.prepend(song.albumartist() + " - ");
|
||||
} else if (!song.is_compilation()) {
|
||||
album_key.prepend(song.artist());
|
||||
}
|
||||
|
||||
if (TokenMatches(tokens, song.title())) {
|
||||
// If the query matched in the song title then we're interested in this
|
||||
// as an individual song.
|
||||
Result result(this);
|
||||
result.type_ = Result::Type_Track;
|
||||
result.metadata_ = song;
|
||||
ret << result;
|
||||
} else {
|
||||
// Otherwise we record this as being an interesting album.
|
||||
albums_with_non_track_matches.insert(album_key);
|
||||
}
|
||||
|
||||
albums.insertMulti(album_key, song);
|
||||
}
|
||||
|
||||
// Add any albums that contained least one song that wasn't matched on the
|
||||
// song title.
|
||||
foreach (const QString& key, albums_with_non_track_matches) {
|
||||
Result result(this);
|
||||
result.type_ = Result::Type_Album;
|
||||
result.metadata_ = albums.value(key);
|
||||
result.album_size_ = albums.count(key);
|
||||
ret << result;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void LibrarySearchProvider::LoadArtAsync(int id, const Result& result) {
|
||||
quint64 loader_id = cover_loader_->Worker()->LoadImageAsync(result.metadata_);
|
||||
cover_loader_tasks_[loader_id] = id;
|
||||
}
|
||||
|
||||
void LibrarySearchProvider::AlbumArtLoaded(quint64 id, const QImage& image) {
|
||||
if (!cover_loader_tasks_.contains(id))
|
||||
return;
|
||||
int orig_id = cover_loader_tasks_.take(id);
|
||||
|
||||
emit ArtLoaded(orig_id, image);
|
||||
}
|
||||
|
||||
void LibrarySearchProvider::LoadTracksAsync(int id, const Result& result) {
|
||||
emit TracksLoaded(id, SongList());
|
||||
}
|
51
src/globalsearch/librarysearchprovider.h
Normal file
51
src/globalsearch/librarysearchprovider.h
Normal file
@ -0,0 +1,51 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef LIBRARYSEARCHPROVIDER_H
|
||||
#define LIBRARYSEARCHPROVIDER_H
|
||||
|
||||
#include "searchprovider.h"
|
||||
#include "core/backgroundthread.h"
|
||||
|
||||
class AlbumCoverLoader;
|
||||
class LibraryBackendInterface;
|
||||
|
||||
|
||||
class LibrarySearchProvider : public BlockingSearchProvider {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LibrarySearchProvider(LibraryBackendInterface* backend, const QString& name,
|
||||
const QIcon& icon, QObject* parent = 0);
|
||||
|
||||
void LoadArtAsync(int id, const Result& result);
|
||||
void LoadTracksAsync(int id, const Result& result);
|
||||
|
||||
protected:
|
||||
ResultList Search(int id, const QString& query);
|
||||
|
||||
private slots:
|
||||
void AlbumArtLoaded(quint64 id, const QImage& image);
|
||||
|
||||
private:
|
||||
LibraryBackendInterface* backend_;
|
||||
|
||||
BackgroundThread<AlbumCoverLoader>* cover_loader_;
|
||||
QMap<quint64, int> cover_loader_tasks_;
|
||||
};
|
||||
|
||||
#endif // LIBRARYSEARCHPROVIDER_H
|
83
src/globalsearch/searchprovider.cpp
Normal file
83
src/globalsearch/searchprovider.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "searchprovider.h"
|
||||
#include "core/boundfuturewatcher.h"
|
||||
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
const int SearchProvider::kArtHeight = 32;
|
||||
|
||||
|
||||
SearchProvider::SearchProvider(const QString& name, const QIcon& icon,
|
||||
QObject* parent)
|
||||
: QObject(parent),
|
||||
name_(name),
|
||||
icon_(icon)
|
||||
{
|
||||
}
|
||||
|
||||
QStringList SearchProvider::TokenizeQuery(const QString& query) {
|
||||
QStringList tokens(query.split(QRegExp("\\s+")));
|
||||
|
||||
for (QStringList::iterator it = tokens.begin() ; it != tokens.end() ; ++it) {
|
||||
(*it).remove('(');
|
||||
(*it).remove(')');
|
||||
(*it).remove('"');
|
||||
|
||||
const int colon = (*it).indexOf(":");
|
||||
if (colon != -1) {
|
||||
(*it).remove(0, colon + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
int SearchProvider::TokenMatches(const QStringList& tokens, const QString& string) {
|
||||
int ret = 0;
|
||||
foreach (const QString& token, tokens) {
|
||||
if (string.contains(token, Qt::CaseInsensitive)) {
|
||||
ret ++;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
BlockingSearchProvider::BlockingSearchProvider(const QString& name, const QIcon& icon, QObject* parent)
|
||||
: SearchProvider(name, icon, parent) {
|
||||
}
|
||||
|
||||
void BlockingSearchProvider::SearchAsync(int id, const QString& query) {
|
||||
QFuture<ResultList> future = QtConcurrent::run(
|
||||
this, &BlockingSearchProvider::Search, id, query);
|
||||
|
||||
BoundFutureWatcher<ResultList, int>* watcher =
|
||||
new BoundFutureWatcher<ResultList, int>(id);
|
||||
watcher->setFuture(future);
|
||||
connect(watcher, SIGNAL(finished()), SLOT(BlockingSearchFinished()));
|
||||
}
|
||||
|
||||
void BlockingSearchProvider::BlockingSearchFinished() {
|
||||
BoundFutureWatcher<ResultList, int>* watcher =
|
||||
static_cast<BoundFutureWatcher<ResultList, int>*>(sender());
|
||||
watcher->deleteLater();
|
||||
|
||||
const int id = watcher->data();
|
||||
emit ResultsAvailable(id, watcher->result());
|
||||
emit SearchFinished(id);
|
||||
}
|
109
src/globalsearch/searchprovider.h
Normal file
109
src/globalsearch/searchprovider.h
Normal file
@ -0,0 +1,109 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SEARCHPROVIDER_H
|
||||
#define SEARCHPROVIDER_H
|
||||
|
||||
#include <QIcon>
|
||||
#include <QMetaType>
|
||||
#include <QObject>
|
||||
|
||||
#include "core/song.h"
|
||||
|
||||
class SearchProvider : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SearchProvider(const QString& name, const QIcon& icon, QObject* parent = 0);
|
||||
|
||||
static const int kArtHeight;
|
||||
|
||||
struct Result {
|
||||
Result(SearchProvider* provider = 0)
|
||||
: provider_(provider), album_size_(0) {}
|
||||
|
||||
enum Type {
|
||||
Type_Track,
|
||||
Type_Album,
|
||||
Type_Stream
|
||||
};
|
||||
|
||||
SearchProvider* provider_;
|
||||
|
||||
Type type_;
|
||||
Song metadata_;
|
||||
|
||||
// How many songs in the album - valid only if type == Type_Album.
|
||||
int album_size_;
|
||||
};
|
||||
typedef QList<Result> ResultList;
|
||||
|
||||
const QString& name() const { return name_; }
|
||||
const QIcon& icon() const { return icon_; }
|
||||
|
||||
// Starts a search. Must emit ResultsAvailable zero or more times and then
|
||||
// SearchFinished exactly once, using this ID.
|
||||
virtual void SearchAsync(int id, const QString& query) = 0;
|
||||
|
||||
// Starts loading an icon for a result that was previously emitted by
|
||||
// ResultsAvailable. Must emit ArtLoaded exactly once with this ID.
|
||||
virtual void LoadArtAsync(int id, const Result& result) = 0;
|
||||
|
||||
// Starts loading tracks for a result that was previously emitted by
|
||||
// ResultsAvailable. Must emit TracksLoaded exactly once with this ID.
|
||||
virtual void LoadTracksAsync(int id, const Result& result) = 0;
|
||||
|
||||
signals:
|
||||
void ResultsAvailable(int id, const SearchProvider::ResultList& results);
|
||||
void SearchFinished(int id);
|
||||
|
||||
void ArtLoaded(int id, const QImage& image);
|
||||
|
||||
void TracksLoaded(int id, const SongList& tracks);
|
||||
|
||||
protected:
|
||||
// These functions treat queries in the same way as LibraryQuery. They're
|
||||
// useful for figuring out whether you got a result because it matched in
|
||||
// the song title or the artist/album name.
|
||||
static QStringList TokenizeQuery(const QString& query);
|
||||
static int TokenMatches(const QStringList& tokens, const QString& string);
|
||||
|
||||
private:
|
||||
QString name_;
|
||||
QIcon icon_;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(SearchProvider::Result)
|
||||
|
||||
|
||||
class BlockingSearchProvider : public SearchProvider {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
BlockingSearchProvider(const QString& name, const QIcon& icon,
|
||||
QObject* parent = 0);
|
||||
|
||||
void SearchAsync(int id, const QString& query);
|
||||
|
||||
protected:
|
||||
virtual ResultList Search(int id, const QString& query) = 0;
|
||||
|
||||
private slots:
|
||||
void BlockingSearchFinished();
|
||||
};
|
||||
|
||||
#endif // SEARCHPROVIDER_H
|
@ -227,6 +227,10 @@ MainWindow::MainWindow(
|
||||
ui_->volume->setValue(volume);
|
||||
VolumeChanged(volume);
|
||||
|
||||
// Initialise the global search widget
|
||||
StyleHelper::setBaseColor(palette().color(QPalette::Highlight).darker());
|
||||
ui_->global_search->Init(library_->backend());
|
||||
|
||||
// Add tabs to the fancy tab widget
|
||||
ui_->tabs->AddTab(library_view_, IconLoader::Load("folder-sound"), tr("Library"));
|
||||
ui_->tabs->AddTab(file_view_, IconLoader::Load("document-open"), tr("Files"));
|
||||
@ -240,7 +244,6 @@ MainWindow::MainWindow(
|
||||
ui_->tabs->AddBottomWidget(ui_->now_playing);
|
||||
|
||||
ui_->tabs->SetBackgroundPixmap(QPixmap(":/sidebar_background.png"));
|
||||
StyleHelper::setBaseColor(palette().color(QPalette::Highlight).darker());
|
||||
|
||||
track_position_timer_->setInterval(1000);
|
||||
connect(track_position_timer_, SIGNAL(timeout()), SLOT(UpdateTrackPosition()));
|
||||
|
@ -35,6 +35,9 @@
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="GlobalSearchWidget" name="global_search" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="FancyTabWidget" name="tabs" native="true"/>
|
||||
</item>
|
||||
@ -866,6 +869,12 @@
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/fancytabwidget.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>GlobalSearchWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>globalsearch/globalsearchwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user