From 9c36cfa199b754051a8ad22ba5dd6fe4aa9008a7 Mon Sep 17 00:00:00 2001 From: David Sansome Date: Mon, 4 Jun 2012 18:18:37 +0100 Subject: [PATCH] Replace the global search widget with a "Search" view on the sidebar. Organise results in a tree automatically. --- src/CMakeLists.txt | 22 +- .../org.clementineplayer.GlobalSearch.xml | 32 - src/globalsearch/common.cpp | 66 -- src/globalsearch/common.h | 92 -- src/globalsearch/globalsearch.cpp | 4 +- src/globalsearch/globalsearchitemdelegate.cpp | 192 ---- src/globalsearch/globalsearchitemdelegate.h | 44 - src/globalsearch/globalsearchservice.cpp | 129 --- src/globalsearch/globalsearchservice.h | 74 -- src/globalsearch/globalsearchsortmodel.cpp | 60 +- src/globalsearch/globalsearchtooltip.cpp | 261 ------ src/globalsearch/globalsearchtooltip.h | 83 -- src/globalsearch/globalsearchview.cpp | 278 ++++++ src/globalsearch/globalsearchview.h | 133 +++ ...balsearchwidget.ui => globalsearchview.ui} | 33 +- src/globalsearch/globalsearchwidget.cpp | 818 ------------------ src/globalsearch/globalsearchwidget.h | 194 ----- .../groovesharksearchprovider.cpp | 52 +- src/globalsearch/icecastsearchprovider.cpp | 5 +- src/globalsearch/librarysearchprovider.cpp | 90 +- src/globalsearch/searchprovider.cpp | 29 +- src/globalsearch/searchprovider.h | 26 +- src/globalsearch/simplesearchprovider.cpp | 31 +- src/globalsearch/spotifysearchprovider.cpp | 46 +- src/globalsearch/tooltipactionwidget.cpp | 130 --- src/globalsearch/tooltipactionwidget.h | 61 -- src/globalsearch/tooltipresultwidget.cpp | 163 ---- src/globalsearch/tooltipresultwidget.h | 58 -- src/globalsearch/urlsearchprovider.cpp | 3 +- src/library/librarymodel.cpp | 12 +- src/library/librarymodel.h | 17 +- src/library/libraryview.cpp | 34 +- src/main.cpp | 3 - src/ui/mainwindow.cpp | 11 +- src/ui/mainwindow.h | 2 + src/ui/mainwindow.ui | 11 +- 36 files changed, 559 insertions(+), 2740 deletions(-) delete mode 100644 src/dbus/org.clementineplayer.GlobalSearch.xml delete mode 100644 src/globalsearch/common.cpp delete mode 100644 src/globalsearch/common.h delete mode 100644 src/globalsearch/globalsearchitemdelegate.cpp delete mode 100644 src/globalsearch/globalsearchitemdelegate.h delete mode 100644 src/globalsearch/globalsearchservice.cpp delete mode 100644 src/globalsearch/globalsearchservice.h delete mode 100644 src/globalsearch/globalsearchtooltip.cpp delete mode 100644 src/globalsearch/globalsearchtooltip.h create mode 100644 src/globalsearch/globalsearchview.cpp create mode 100644 src/globalsearch/globalsearchview.h rename src/globalsearch/{globalsearchwidget.ui => globalsearchview.ui} (55%) delete mode 100644 src/globalsearch/globalsearchwidget.cpp delete mode 100644 src/globalsearch/globalsearchwidget.h delete mode 100644 src/globalsearch/tooltipactionwidget.cpp delete mode 100644 src/globalsearch/tooltipactionwidget.h delete mode 100644 src/globalsearch/tooltipresultwidget.cpp delete mode 100644 src/globalsearch/tooltipresultwidget.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8599fc67e..cddc7b705 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -136,14 +136,11 @@ set(SOURCES engines/gstenginepipeline.cpp engines/gstelementdeleter.cpp - globalsearch/common.cpp globalsearch/digitallyimportedsearchprovider.cpp globalsearch/globalsearch.cpp - globalsearch/globalsearchitemdelegate.cpp globalsearch/globalsearchsettingspage.cpp globalsearch/globalsearchsortmodel.cpp - globalsearch/globalsearchtooltip.cpp - globalsearch/globalsearchwidget.cpp + globalsearch/globalsearchview.cpp globalsearch/groovesharksearchprovider.cpp globalsearch/icecastsearchprovider.cpp globalsearch/librarysearchprovider.cpp @@ -151,8 +148,6 @@ set(SOURCES globalsearch/searchprovider.cpp globalsearch/simplesearchprovider.cpp globalsearch/somafmsearchprovider.cpp - globalsearch/tooltipactionwidget.cpp - globalsearch/tooltipresultwidget.cpp globalsearch/urlsearchprovider.cpp internet/digitallyimportedclient.cpp @@ -418,13 +413,10 @@ set(HEADERS globalsearch/globalsearch.h globalsearch/globalsearchsettingspage.h - globalsearch/globalsearchtooltip.h - globalsearch/globalsearchwidget.h + globalsearch/globalsearchview.h globalsearch/groovesharksearchprovider.h globalsearch/searchprovider.h globalsearch/simplesearchprovider.h - globalsearch/tooltipactionwidget.h - globalsearch/tooltipresultwidget.h internet/digitallyimportedclient.h internet/digitallyimportedservicebase.h @@ -610,7 +602,7 @@ set(UI devices/deviceproperties.ui globalsearch/globalsearchsettingspage.ui - globalsearch/globalsearchwidget.ui + globalsearch/globalsearchview.ui internet/digitallyimportedsettingspage.ui internet/groovesharksettingspage.ui @@ -866,12 +858,6 @@ if(HAVE_DBUS) dbus/udisksdevice) endif(HAVE_DEVICEKIT) - # Global search interface - qt4_add_dbus_adaptor(SOURCES - dbus/org.clementineplayer.GlobalSearch.xml - globalsearch/globalsearchservice.h GlobalSearchService - globalsearch/globalsearchadaptor) - # Wiimotedev interface classes if(ENABLE_WIIMOTEDEV) qt4_add_dbus_interface(SOURCES @@ -885,13 +871,11 @@ optional_source(HAVE_DBUS core/mpris.cpp core/mpris1.cpp core/mpris2.cpp - globalsearch/globalsearchservice.cpp ui/dbusscreensaver.cpp HEADERS core/mpris.h core/mpris1.h core/mpris2.h - globalsearch/globalsearchservice.h ) optional_source(HAVE_WIIMOTEDEV diff --git a/src/dbus/org.clementineplayer.GlobalSearch.xml b/src/dbus/org.clementineplayer.GlobalSearch.xml deleted file mode 100644 index a0c7b0030..000000000 --- a/src/dbus/org.clementineplayer.GlobalSearch.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/globalsearch/common.cpp b/src/globalsearch/common.cpp deleted file mode 100644 index df7711d50..000000000 --- a/src/globalsearch/common.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* This file is part of Clementine. - Copyright 2011, David Sansome - - 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 . -*/ - -#include "common.h" - -#ifdef HAVE_DBUS - -QDBusArgument& operator <<(QDBusArgument& arg, const GlobalSearchServiceResult& result) { - arg.beginStructure(); - arg << result.result_id_ - << result.art_on_the_way_ - << result.provider_name_ - << result.type_ - << result.match_quality_ - << result.album_size_ - << result.title_ - << result.artist_ - << result.album_ - << result.album_artist_ - << result.is_compilation_ - << result.track_; - arg.endStructure(); - - return arg; -} - -const QDBusArgument& operator >>(const QDBusArgument& arg, GlobalSearchServiceResult& result) { - int type; - int match_quality; - - arg.beginStructure(); - arg >> result.result_id_ - >> result.art_on_the_way_ - >> result.provider_name_ - >> type - >> match_quality - >> result.album_size_ - >> result.title_ - >> result.artist_ - >> result.album_ - >> result.album_artist_ - >> result.is_compilation_ - >> result.track_; - arg.endStructure(); - - result.type_ = static_cast(type); - result.match_quality_ = static_cast(match_quality); - - return arg; -} - -#endif // HAVE_DBUS diff --git a/src/globalsearch/common.h b/src/globalsearch/common.h deleted file mode 100644 index ce9d2fdaa..000000000 --- a/src/globalsearch/common.h +++ /dev/null @@ -1,92 +0,0 @@ -/* This file is part of Clementine. - Copyright 2011, David Sansome - - 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 . -*/ - -#ifndef GLOBALSEARCHSERVICERESULT_H -#define GLOBALSEARCHSERVICERESULT_H - -// This file contains definitions that are shared between Clementine and any -// external applications that need to do global searches over DBus. - -#include "config.h" - -#ifdef HAVE_DBUS -# include -#endif - -#include - -namespace globalsearch { - -// The order of types here is the order they'll appear in the UI. -enum Type { - Type_Track = 0, - Type_Stream, - Type_Album -}; - -enum MatchQuality { - // A token in the search string matched at the beginning of the song - // metadata. - Quality_AtStart = 0, - - // A token matched somewhere else. - Quality_Middle, - - Quality_None -}; - -} // namespace globalsearch - - -#ifdef HAVE_DBUS - -struct GlobalSearchServiceResult { - // When adding new fields to this struct remember to update the dbus signature - // which is duplicated in the xml specification and in clementinerunner.cpp - - int result_id_; - bool art_on_the_way_; - - QString provider_name_; - globalsearch::Type type_; - globalsearch::MatchQuality match_quality_; - - int album_size_; - - QString title_; - QString artist_; - QString album_; - QString album_artist_; - bool is_compilation_; - int track_; - - // Not included in the dbus emission. - QIcon image_; - int provider_order_; -}; -typedef QList GlobalSearchServiceResultList; - -Q_DECLARE_METATYPE(GlobalSearchServiceResult) -Q_DECLARE_METATYPE(GlobalSearchServiceResultList) - -QDBusArgument& operator <<(QDBusArgument& arg, const GlobalSearchServiceResult& result); -const QDBusArgument& operator >>(const QDBusArgument& arg, GlobalSearchServiceResult& result); - -#endif // HAVE_DBUS - - -#endif // GLOBALSEARCHSERVICERESULT_H diff --git a/src/globalsearch/globalsearch.cpp b/src/globalsearch/globalsearch.cpp index dcc444f0e..6df589800 100644 --- a/src/globalsearch/globalsearch.cpp +++ b/src/globalsearch/globalsearch.cpp @@ -140,8 +140,8 @@ void GlobalSearch::timerEvent(QTimerEvent* e) { } QString GlobalSearch::PixmapCacheKey(const SearchProvider::Result& result) const { - return QString::number(qulonglong(result.provider_)) - % "," % QString::number(int(result.type_)) + return "globalsearch:" + % QString::number(qulonglong(result.provider_)) % "," % result.metadata_.url().toString(); } diff --git a/src/globalsearch/globalsearchitemdelegate.cpp b/src/globalsearch/globalsearchitemdelegate.cpp deleted file mode 100644 index c9512b3ce..000000000 --- a/src/globalsearch/globalsearchitemdelegate.cpp +++ /dev/null @@ -1,192 +0,0 @@ -/* This file is part of Clementine. - Copyright 2010, David Sansome - - 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 . -*/ - -#include "globalsearchitemdelegate.h" -#include "globalsearchwidget.h" -#include "searchprovider.h" -#include "core/logging.h" - -#include -#include - - -const int GlobalSearchItemDelegate::kHeight = SearchProvider::kArtHeight; -const int GlobalSearchItemDelegate::kMargin = 2; -const int GlobalSearchItemDelegate::kArtMargin = 6; -const int GlobalSearchItemDelegate::kProviderIconSize = 16; - -GlobalSearchItemDelegate::GlobalSearchItemDelegate(GlobalSearchWidget* widget) - : QStyledItemDelegate(widget), - widget_(widget) -{ - no_cover_ = QPixmap::fromImage(SearchProvider::ScaleAndPad(QImage(":nocover.png"))); -} - -QSize GlobalSearchItemDelegate::sizeHint(const QStyleOptionViewItem& option, - const QModelIndex& index) const { - QSize size = QStyledItemDelegate::sizeHint(option, index); - size.setHeight(kHeight + kMargin*2); - return size; -} - -void GlobalSearchItemDelegate::paint(QPainter* p, - const QStyleOptionViewItem& option, - const QModelIndex& index) const { - const SearchProvider::Result result = - index.data(GlobalSearchWidget::Role_PrimaryResult).value(); - const SearchProvider::ResultList all_results = - index.data(GlobalSearchWidget::Role_AllResults).value(); - const Song& m = result.metadata_; - const bool selected = option.state & QStyle::State_Selected; - - widget_->LazyLoadArt(index); - - QFont bold_font = option.font; - bold_font.setBold(true); - - QFont big_font(bold_font); - big_font.setPointSizeF(big_font.pointSizeF() + 2); - - QColor pen = option.palette.color(selected ? QPalette::HighlightedText : QPalette::Text); - QColor light_pen = pen; - pen.setAlpha(200); - light_pen.setAlpha(128); - - // Draw the background - const QStyleOptionViewItemV3* vopt = qstyleoption_cast(&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() + kMargin, kHeight, kHeight); - - QPixmap art = index.data(Qt::DecorationRole).value(); - if (art.isNull()) - art = no_cover_; - - p->drawPixmap(art_rect, art); - - // Draw a track count indicator next to the art. - QRect count_rect(art_rect.right() + kArtMargin, art_rect.top(), - kHeight, kHeight); - - QString count; - switch (result.type_) { - case globalsearch::Type_Track: - break; - - case globalsearch::Type_Stream: - count = QString::fromUtf8("∞"); - break; - - case globalsearch::Type_Album: - if (result.album_size_ <= 0) - count = "-"; - else - count = QString::number(result.album_size_); - break; - } - - p->setPen(light_pen); - p->setFont(big_font); - p->drawText(count_rect, Qt::TextSingleLine | Qt::AlignCenter, count); - - // Draw provider icons on the right. - const int icons_width = (kProviderIconSize + kArtMargin) * all_results.count(); - QRect icons_rect(rect.right() - icons_width, - rect.top() + (rect.height() - kProviderIconSize) / 2, - icons_width, kProviderIconSize); - - QRect icon_rect(icons_rect.topLeft(), QSize(kProviderIconSize, kProviderIconSize)); - foreach (const SearchProvider::Result& result, all_results) { - p->drawPixmap(icon_rect, result.provider_->icon().pixmap(kProviderIconSize)); - icon_rect.translate(kProviderIconSize + kArtMargin, 0); - } - - // Position text - QRect text_rect(count_rect.right() + kArtMargin, count_rect.top(), - icons_rect.left() - count_rect.right() - kArtMargin*2, 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)); - - QString line_1; - QString line_2; - - // The text we draw depends on the type of result. - switch (result.type_) { - case globalsearch::Type_Track: - case globalsearch::Type_Stream: { - // Title - line_1 += m.PrettyTitle() + " "; - - // Artist - Album - Track n - if (!m.artist().isEmpty()) { - line_2 += m.artist(); - } else if (!m.albumartist().isEmpty()) { - line_2 += m.albumartist(); - } - - if (!m.album().isEmpty()) { - line_2 += " - " + m.album(); - } - - if (m.track() > 0) { - line_2 += " - " + tr("track %1").arg(m.track()); - } - - break; - } - - case globalsearch::Type_Album: { - // Line 1 is Artist - Album - // Artist - if (!m.albumartist().isEmpty()) - line_1 += m.albumartist(); - else if (m.is_compilation()) - line_1 += tr("Various artists"); - else if (!m.artist().isEmpty()) - line_1 += m.artist(); - else - line_1 += tr("Unknown"); - - // Dash - line_1 += " - "; - - // Album - if (m.album().isEmpty()) - line_1 += tr("Unknown"); - else - line_1 += m.album(); - - break; - } - } - - if (line_2.isEmpty()) { - text_rect_1 = text_rect; - } - - p->setFont(bold_font); - p->setPen(pen); - p->drawText(text_rect_1, Qt::TextSingleLine | Qt::AlignVCenter, line_1); - - p->setFont(option.font); - p->setPen(light_pen); - p->drawText(text_rect_2, Qt::TextSingleLine | Qt::AlignVCenter, line_2); -} diff --git a/src/globalsearch/globalsearchitemdelegate.h b/src/globalsearch/globalsearchitemdelegate.h deleted file mode 100644 index c9a7095da..000000000 --- a/src/globalsearch/globalsearchitemdelegate.h +++ /dev/null @@ -1,44 +0,0 @@ -/* This file is part of Clementine. - Copyright 2010, David Sansome - - 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 . -*/ - -#ifndef GLOBALSEARCHITEMDELEGATE_H -#define GLOBALSEARCHITEMDELEGATE_H - -#include - -class GlobalSearchWidget; - - -class GlobalSearchItemDelegate : public QStyledItemDelegate { -public: - GlobalSearchItemDelegate(GlobalSearchWidget* widget); - - static const int kHeight; - static const int kMargin; - static const int kArtMargin; - static const int kProviderIconSize; - - QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; - void paint(QPainter* painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const; - -private: - GlobalSearchWidget* widget_; - QPixmap no_cover_; -}; - -#endif // GLOBALSEARCHITEMDELEGATE_H diff --git a/src/globalsearch/globalsearchservice.cpp b/src/globalsearch/globalsearchservice.cpp deleted file mode 100644 index d693a4220..000000000 --- a/src/globalsearch/globalsearchservice.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* This file is part of Clementine. - Copyright 2011, David Sansome - - 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 . -*/ - -#include "globalsearch.h" -#include "globalsearchservice.h" -#include "globalsearch/globalsearchadaptor.h" -#include "core/logging.h" -#include "core/mpris_common.h" - - -GlobalSearchService::GlobalSearchService(GlobalSearch* engine, QObject* parent) - : QObject(parent), - engine_(engine) -{ - qDBusRegisterMetaType(); - qDBusRegisterMetaType(); - - new GlobalSearchAdaptor(this); - QDBusConnection::sessionBus().registerObject("/GlobalSearch", this); - - connect(engine_, SIGNAL(ResultsAvailable(int,SearchProvider::ResultList)), - this, SLOT(ResultsAvailableSlot(int,SearchProvider::ResultList)), - Qt::QueuedConnection); - connect(engine_, SIGNAL(SearchFinished(int)), - this, SLOT(SearchFinishedSlot(int)), - Qt::QueuedConnection); - connect(engine_, SIGNAL(ArtLoaded(int,QPixmap)), - this, SLOT(ArtLoadedSlot(int,QPixmap)), - Qt::QueuedConnection); -} - -int GlobalSearchService::StartSearch(const QString& query, bool prefetch_art) { - PendingSearch pending_search; - pending_search.prefetch_art_ = prefetch_art; - - const int id = engine_->SearchAsync(query); - pending_searches_[id] = pending_search; - return id; -} - -void GlobalSearchService::CancelSearch(int id) { - if (!pending_searches_.contains(id)) - return; - - engine_->CancelSearch(id); - pending_searches_.remove(id); -} - -void GlobalSearchService::ResultsAvailableSlot(int id, const SearchProvider::ResultList& results) { - if (!pending_searches_.contains(id)) - return; - - const PendingSearch& pending = pending_searches_[id]; - - GlobalSearchServiceResultList ret; - foreach (const SearchProvider::Result& result, results) { - const int result_id = next_result_id_ ++; - - RecentResult* recent = &recent_results_[result_id]; - recent->result_.art_on_the_way_ = false; - - // Prefetch art if it was requested - if (pending.prefetch_art_ && !result.provider_->art_is_probably_remote()) { - const int art_id = engine_->LoadArtAsync(result); - prefetching_art_[art_id] = result_id; - recent->result_.art_on_the_way_ = true; - } - - // Build the result to send back - recent->result_.result_id_ = result_id; - recent->result_.provider_name_ = result.provider_->name(); - recent->result_.type_ = result.type_; - recent->result_.match_quality_ = result.match_quality_; - - recent->result_.album_size_ = result.album_size_; - - recent->result_.title_ = result.metadata_.title(); - recent->result_.artist_ = result.metadata_.artist(); - recent->result_.album_ = result.metadata_.album(); - recent->result_.album_artist_ = result.metadata_.albumartist(); - recent->result_.is_compilation_ = result.metadata_.is_compilation(); - recent->result_.track_ = result.metadata_.track(); - - ret << recent->result_; - } - - emit ResultsAvailable(id, ret); -} - -void GlobalSearchService::SearchFinishedSlot(int id) { - if (!pending_searches_.contains(id)) - return; - - emit SearchFinished(id); - pending_searches_.remove(id); -} - -void GlobalSearchService::ArtLoadedSlot(int id, const QPixmap& pixmap) { - QMap::iterator it = prefetching_art_.find(id); - if (it == prefetching_art_.end()) - return; - - const int result_id = prefetching_art_.take(id); - QMap::iterator it2 = recent_results_.find(result_id); - if (it2 == recent_results_.end()) - return; - - // Encode the pixmap as a png - QBuffer buf; - buf.open(QIODevice::WriteOnly); - pixmap.toImage().save(&buf, "PNG"); - buf.close(); - - emit ArtLoaded(result_id, buf.data()); -} diff --git a/src/globalsearch/globalsearchservice.h b/src/globalsearch/globalsearchservice.h deleted file mode 100644 index 9d9171ec7..000000000 --- a/src/globalsearch/globalsearchservice.h +++ /dev/null @@ -1,74 +0,0 @@ -/* This file is part of Clementine. - Copyright 2011, David Sansome - - 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 . -*/ - -#ifndef GLOBALSEARCHSERVICE_H -#define GLOBALSEARCHSERVICE_H - -#include -#include -#include - -#include "common.h" -#include "searchprovider.h" - -class GlobalSearch; - -class QEventLoop; - - -class GlobalSearchService : public QObject { - Q_OBJECT - -public: - GlobalSearchService(GlobalSearch* engine, QObject* parent = 0); - -public slots: - int StartSearch(const QString& query, bool prefetch_art); - void CancelSearch(int id); - -signals: - void ResultsAvailable(int id, const GlobalSearchServiceResultList& results); - void SearchFinished(int id); - void ArtLoaded(int result_id, const QByteArray& image_data); - -private slots: - void ResultsAvailableSlot(int id, const SearchProvider::ResultList& results); - void SearchFinishedSlot(int id); - void ArtLoadedSlot(int id, const QPixmap& pixmap); - -private: - struct PendingSearch { - bool prefetch_art_; - }; - - struct RecentResult { - GlobalSearchServiceResult result_; - }; - - GlobalSearch* engine_; - - // GlobalSearch request ids - QMap pending_searches_; - - // Result ids - QMap recent_results_; - QMap prefetching_art_; // LoadArt id -> result id - - int next_result_id_; -}; - -#endif // GLOBALSEARCHSERVICE_H diff --git a/src/globalsearch/globalsearchsortmodel.cpp b/src/globalsearch/globalsearchsortmodel.cpp index eb79022ae..14fc9d5ac 100644 --- a/src/globalsearch/globalsearchsortmodel.cpp +++ b/src/globalsearch/globalsearchsortmodel.cpp @@ -15,8 +15,8 @@ along with Clementine. If not, see . */ -#include "globalsearchwidget.h" #include "globalsearchsortmodel.h" +#include "globalsearchview.h" #include "searchprovider.h" #include "core/logging.h" @@ -26,22 +26,38 @@ GlobalSearchSortModel::GlobalSearchSortModel(QObject* parent) } bool GlobalSearchSortModel::lessThan(const QModelIndex& left, const QModelIndex& right) const { - const SearchProvider::Result r1 = left.data(GlobalSearchWidget::Role_PrimaryResult) - .value(); - const SearchProvider::Result r2 = right.data(GlobalSearchWidget::Role_PrimaryResult) - .value(); + // Compare the provider sort index first. + const int index_left = left.data(GlobalSearchView::Role_ProviderIndex).toInt(); + const int index_right = right.data(GlobalSearchView::Role_ProviderIndex).toInt(); + if (index_left < index_right) return true; + if (index_left > index_right) return false; - // Order results that arrived first first, so that the results don't jump - // around while the user is trying to navigate through them. - const int order_left = left.data(GlobalSearchWidget::Role_OrderArrived).toInt(); - const int order_right = right.data(GlobalSearchWidget::Role_OrderArrived).toInt(); + // Dividers always go first + if (left.data(LibraryModel::Role_IsDivider).toBool()) return true; + if (right.data(LibraryModel::Role_IsDivider).toBool()) return false; - if (order_left < order_right) return true; - if (order_left > order_right) return false; + // Containers go before songs if they're at the same level + const bool left_is_container = left.data(LibraryModel::Role_ContainerType).isValid(); + const bool right_is_container = right.data(LibraryModel::Role_ContainerType).isValid(); + if (left_is_container && !right_is_container) return true; + if (right_is_container && !left_is_container) return false; + + // Containers get sorted on their sort text. + if (left_is_container) { + return QString::localeAwareCompare( + left.data(LibraryModel::Role_SortText).toString(), + right.data(LibraryModel::Role_SortText).toString()) < 0; + } + + // Otherwise we're comparing songs. Sort by disc, track, then title. + const SearchProvider::Result r1 = left.data(GlobalSearchView::Role_Result) + .value(); + const SearchProvider::Result r2 = right.data(GlobalSearchView::Role_Result) + .value(); #define CompareInt(field) \ - if (r1.field < r2.field) return true; \ - if (r1.field > r2.field) return false + if (r1.metadata_.field() < r2.metadata_.field()) return true; \ + if (r1.metadata_.field() > r2.metadata_.field()) return false int ret = 0; @@ -50,21 +66,9 @@ bool GlobalSearchSortModel::lessThan(const QModelIndex& left, const QModelIndex& if (ret < 0) return true; \ if (ret > 0) return false - // If they arrived at the same time then sort by quality and type. - CompareInt(match_quality_); - CompareInt(type_); - - // Failing that, compare title, artist and album - switch (r1.type_) { - case globalsearch::Type_Track: - case globalsearch::Type_Stream: - CompareString(title); - // fallthrough - case globalsearch::Type_Album: - CompareString(artist); - CompareString(album); - break; - } + CompareInt(disc); + CompareInt(track); + CompareString(title); return false; diff --git a/src/globalsearch/globalsearchtooltip.cpp b/src/globalsearch/globalsearchtooltip.cpp deleted file mode 100644 index 07b2bbf6b..000000000 --- a/src/globalsearch/globalsearchtooltip.cpp +++ /dev/null @@ -1,261 +0,0 @@ -/* This file is part of Clementine. - Copyright 2010, David Sansome - - 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 . -*/ - -#include "globalsearch.h" -#include "globalsearchtooltip.h" -#include "tooltipactionwidget.h" -#include "tooltipresultwidget.h" -#include "core/logging.h" -#include "core/utilities.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -const qreal GlobalSearchTooltip::kBorderRadius = 8.0; -const qreal GlobalSearchTooltip::kTotalBorderWidth = 4.0; -const qreal GlobalSearchTooltip::kOuterBorderWidth = 0.5; -const qreal GlobalSearchTooltip::kArrowWidth = 10.0; -const qreal GlobalSearchTooltip::kArrowHeight = 10.0; - - -GlobalSearchTooltip::GlobalSearchTooltip(QWidget* event_target) - : QWidget(NULL), - desktop_(qApp->desktop()), - event_target_(event_target), - active_result_(0), - show_tooltip_help_(true) -{ - setWindowFlags(Qt::FramelessWindowHint | Qt::Popup); - setFocusPolicy(Qt::NoFocus); - setAttribute(Qt::WA_NoSystemBackground); - setAttribute(Qt::WA_TranslucentBackground); - - switch_action_ = new QAction(tr("Switch provider"), this); - switch_action_->setShortcut(QKeySequence(Qt::Key_Tab)); - connect(switch_action_, SIGNAL(triggered()), SLOT(SwitchProvider())); - - ReloadSettings(); -} - -void GlobalSearchTooltip::SetResults(const SearchProvider::ResultList& results) { - results_ = results; - - qDeleteAll(widgets_); - widgets_.clear(); - result_buttons_.clear(); - - active_result_ = 0; - - // Using a QVBoxLayout here made some weird flickering that I couldn't figure - // out how to fix, so do layout manually. - int w = 0; - int y = 9; - - // Add a widget for each result - foreach (const SearchProvider::Result& result, results) { - TooltipResultWidget* widget = new TooltipResultWidget(result, this); - if (widgets_.isEmpty()) { - // If this is the first widget then mark it as selected - widget->setChecked(true); - } - - AddWidget(widget, &w, &y); - result_buttons_ << widget; - } - - if (show_tooltip_help_) { - // Add the action widget - QList actions; - if (results_.count() > 1) { - actions.append(switch_action_); - } - actions.append(common_actions_); - - action_widget_ = new TooltipActionWidget(this); - action_widget_->SetActions(actions); - AddWidget(action_widget_, &w, &y); - } - - // Set the width of each widget - foreach (QWidget* widget, widgets_) { - widget->resize(w, widget->sizeHint().height()); - } - - // Resize this widget - y += 9; - resize(w, y); - - inner_rect_ = rect().adjusted( - kArrowWidth + kTotalBorderWidth, kTotalBorderWidth, - -kTotalBorderWidth, -kTotalBorderWidth); - - foreach (QWidget* widget, widgets_) { - widget->setMask(inner_rect_); - } -} - -void GlobalSearchTooltip::AddWidget(QWidget* widget, int* w, int* y) { - widget->move(0, *y); - widget->show(); - widgets_ << widget; - - QSize size_hint(widget->sizeHint()); - *y += size_hint.height(); - *w = qMax(*w, size_hint.width()); -} - -void GlobalSearchTooltip::ShowAt(const QPoint& pointing_to) { - const qreal min_arrow_offset = kBorderRadius + kArrowHeight; - const QRect screen = desktop_->screenGeometry(this); - - arrow_offset_ = min_arrow_offset + - qMax(0, pointing_to.y() + height() - screen.bottom()); - - move(pointing_to.x(), pointing_to.y() - arrow_offset_); - - if (!isVisible()) - show(); -} - -bool GlobalSearchTooltip::event(QEvent* e) { - switch (e->type()) { - case QEvent::KeyPress: { - QKeyEvent* ke = static_cast(e); - if (ke->key() == Qt::Key_Tab && ke->modifiers() == Qt::NoModifier) { - SwitchProvider(); - e->accept(); - return true; - } - - // fallthrough - } - case QEvent::KeyRelease: - case QEvent::InputMethod: - case QEvent::Shortcut: - case QEvent::ShortcutOverride: - if (QApplication::sendEvent(event_target_, e)) { - return true; - } - break; - - case QEvent::MouseButtonPress: - case QEvent::MouseButtonRelease: - case QEvent::MouseButtonDblClick: - if (!underMouse()) { - const QMouseEvent* me = static_cast(e); - QWidget* child = event_target_->childAt( - event_target_->mapFromGlobal(me->globalPos())); - - if (child) - child->setAttribute(Qt::WA_UnderMouse, true); - - Utilities::ForwardMouseEvent(me, child ? child : event_target_); - return true; - } - break; - - default: - break; - } - - return QWidget::event(e); -} - -void GlobalSearchTooltip::paintEvent(QPaintEvent*) { - QPainter p(this); - - const QColor outer_color(0, 0, 0, 192); - const QColor inner_color = palette().color(QPalette::Highlight); - const QColor center_color = palette().color(QPalette::Base); - - // Transparent background - p.fillRect(rect(), Qt::transparent); - - QRect area(inner_rect_.adjusted( - -kTotalBorderWidth/2, -kTotalBorderWidth/2, - kTotalBorderWidth/2, kTotalBorderWidth/2)); - - // Draw the border - p.setCompositionMode(QPainter::CompositionMode_Source); - p.setRenderHint(QPainter::Antialiasing); - p.setPen(QPen(outer_color, kTotalBorderWidth)); - p.drawRoundedRect(area, kBorderRadius, kBorderRadius); - - // Draw the arrow - QPolygonF arrow; - arrow << QPointF(kArrowWidth + 2, arrow_offset_ - kArrowHeight) - << QPointF(0, arrow_offset_) - << QPointF(kArrowWidth + 2, arrow_offset_ + kArrowHeight); - - p.setBrush(outer_color); - p.setPen(outer_color); - p.drawPolygon(arrow); - - // Now draw the inner shapes on top - const qreal inner_border_width = kTotalBorderWidth - kOuterBorderWidth; - - QRect inner_area(inner_rect_.adjusted( - -inner_border_width/2, -inner_border_width/2, - inner_border_width/2, inner_border_width/2)); - - // Inner border - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - p.setBrush(center_color); - p.setPen(QPen(inner_color, inner_border_width)); - p.drawRoundedRect(inner_area, kBorderRadius, kBorderRadius); - - // Inner arrow - arrow[0].setY(arrow[0].y() + kOuterBorderWidth + 0.5); - arrow[1].setX(arrow[1].x() + kOuterBorderWidth + 1.5); - arrow[2].setY(arrow[2].y() - kOuterBorderWidth - 0.5); - - p.setBrush(inner_color); - p.setPen(inner_color); - p.drawPolygon(arrow); -} - -void GlobalSearchTooltip::SwitchProvider() { - // Find which one was checked before. - int old_index = -1; - for (int i=0 ; iisChecked()) { - old_index = i; - break; - } - } - - if (old_index == -1) - return; - - // Check the new one. The auto exclusive group will take care of unchecking - // the old one. - active_result_ = (old_index + 1) % result_buttons_.count(); - result_buttons_[active_result_]->setChecked(true); -} - -void GlobalSearchTooltip::ReloadSettings() { - QSettings s; - s.beginGroup(GlobalSearch::kSettingsGroup); - - show_tooltip_help_ = s.value("tooltip_help", true).toBool(); -} diff --git a/src/globalsearch/globalsearchtooltip.h b/src/globalsearch/globalsearchtooltip.h deleted file mode 100644 index e7ee7945d..000000000 --- a/src/globalsearch/globalsearchtooltip.h +++ /dev/null @@ -1,83 +0,0 @@ -/* This file is part of Clementine. - Copyright 2010, David Sansome - - 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 . -*/ - -#ifndef GLOBALSEARCHTOOLTIP_H -#define GLOBALSEARCHTOOLTIP_H - -#include "searchprovider.h" - -#include - -class QAbstractButton; -class QDesktopWidget; - -class TooltipActionWidget; - -class GlobalSearchTooltip : public QWidget { - Q_OBJECT - -public: - GlobalSearchTooltip(QWidget* event_target); - - static const qreal kBorderRadius; - static const qreal kTotalBorderWidth; - static const qreal kOuterBorderWidth; - static const qreal kArrowWidth; - static const qreal kArrowHeight; - - void SetActions(const QList& actions) { common_actions_ = actions; } - void SetResults(const SearchProvider::ResultList& results); - void ShowAt(const QPoint& pointing_to); - - int ActiveResultIndex() const { return active_result_; } - qreal ArrowOffset() const; - - bool event(QEvent* e); - -public slots: - void ReloadSettings(); - -protected: - void paintEvent(QPaintEvent*); - -private slots: - void SwitchProvider(); - -private: - void AddWidget(QWidget* widget, int* w, int* y); - -private: - QDesktopWidget* desktop_; - TooltipActionWidget* action_widget_; - QList common_actions_; - - QAction* switch_action_; - - SearchProvider::ResultList results_; - qreal arrow_offset_; - QRect inner_rect_; - - QWidget* event_target_; - - QWidgetList widgets_; - QList result_buttons_; - int active_result_; - - bool show_tooltip_help_; -}; - -#endif // GLOBALSEARCHTOOLTIP_H diff --git a/src/globalsearch/globalsearchview.cpp b/src/globalsearch/globalsearchview.cpp new file mode 100644 index 000000000..f37232d45 --- /dev/null +++ b/src/globalsearch/globalsearchview.cpp @@ -0,0 +1,278 @@ +/* This file is part of Clementine. + Copyright 2012, David Sansome + + 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 . +*/ + +#include "globalsearch.h" +#include "globalsearchsortmodel.h" +#include "globalsearchview.h" +#include "searchprovider.h" +#include "ui_globalsearchview.h" +#include "core/application.h" +#include "core/logging.h" +#include "core/mimedata.h" +#include "library/librarymodel.h" +#include "library/libraryview.h" + +#include +#include +#include + +const int GlobalSearchView::kSwapModelsTimeoutMsec = 250; + +GlobalSearchView::GlobalSearchView(Application* app, QWidget* parent) + : QWidget(parent), + app_(app), + engine_(app_->global_search()), + ui_(new Ui_GlobalSearchView), + last_search_id_(0), + 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_), + swap_models_timer_(new QTimer(this)) +{ + ui_->setupUi(this); + + connect(ui_->search, SIGNAL(textChanged(QString)), SLOT(TextEdited(QString))); + + ui_->results->setItemDelegate(new LibraryItemDelegate(this)); + + group_by_[0] = LibraryModel::GroupBy_Artist; + group_by_[1] = LibraryModel::GroupBy_Album; + group_by_[2] = LibraryModel::GroupBy_None; + + // Set up the sorting proxy model + 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); + + swap_models_timer_->setSingleShot(true); + swap_models_timer_->setInterval(kSwapModelsTimeoutMsec); + connect(swap_models_timer_, SIGNAL(timeout()), SLOT(SwapModels())); + + // These have to be queued connections because they may get emitted before + // our call to Search() (or whatever) returns and we add the ID to the map. + connect(engine_, SIGNAL(ResultsAvailable(int,SearchProvider::ResultList)), + SLOT(AddResults(int,SearchProvider::ResultList)), + Qt::QueuedConnection); + connect(engine_, SIGNAL(ArtLoaded(int,QPixmap)), SLOT(ArtLoaded(int,QPixmap)), + Qt::QueuedConnection); + connect(engine_, SIGNAL(TracksLoaded(int,MimeData*)), SLOT(TracksLoaded(int,MimeData*)), + Qt::QueuedConnection); +} + +GlobalSearchView::~GlobalSearchView() { + delete ui_; +} + +void GlobalSearchView::StartSearch(const QString& query) { + ui_->search->set_text(query); + TextEdited(query.trimmed()); + + // Swap models immediately + swap_models_timer_->stop(); + SwapModels(); +} + +void GlobalSearchView::TextEdited(const QString& text) { + const QString trimmed(text.trimmed()); + + // Add results to the back model, switch models after some delay. + provider_sort_indices_.clear(); + containers_.clear(); + next_provider_sort_index_ = 1000; + back_model_->clear(); + current_model_ = back_model_; + current_proxy_ = back_proxy_; + swap_models_timer_->start(); + + // Cancel the last search (if any) and start the new one. + engine_->CancelSearch(last_search_id_); + // If text query is empty, don't start a new search + if (trimmed.isEmpty()) { + last_search_id_ = -1; + return; + } + last_search_id_ = engine_->SearchAsync(trimmed); +} + +void GlobalSearchView::AddResults(int id, const SearchProvider::ResultList& results) { + if (id != last_search_id_ || results.isEmpty()) + return; + + int sort_index = 0; + + // Create a divider for this provider if we haven't seen it before. + SearchProvider* provider = results.first().provider_; + + if (!provider_sort_indices_.contains(provider)) { + // TODO: Check if the user has configured a sort order for this provider. + sort_index = next_provider_sort_index_ ++; + + QStandardItem* divider = new QStandardItem(provider->icon(), provider->name()); + divider->setData(true, LibraryModel::Role_IsDivider); + divider->setData(sort_index, Role_ProviderIndex); + current_model_->appendRow(divider); + + provider_sort_indices_[provider] = sort_index; + } else { + sort_index = provider_sort_indices_[provider]; + } + + foreach (const SearchProvider::Result& result, results) { + QStandardItem* parent = current_model_->invisibleRootItem(); + + // Find (or create) the container nodes for this result if we can. + if (result.group_automatically_) { + ContainerKey key; + key.provider_index_ = sort_index; + + parent = BuildContainers(result.metadata_, parent, &key); + } + + // Create the item + QStandardItem* item = new QStandardItem(result.metadata_.title()); + item->setData(QVariant::fromValue(result), Role_Result); + item->setData(sort_index, Role_ProviderIndex); + + parent->appendRow(item); + } +} + +QStandardItem* GlobalSearchView::BuildContainers( + const Song& s, QStandardItem* parent, ContainerKey* key, int level) { + if (level >= 3) { + return parent; + } + + QString display_text; + QString sort_text; + int year = 0; + + switch (group_by_[level]) { + case LibraryModel::GroupBy_Artist: + display_text = LibraryModel::TextOrUnknown(s.artist()); + sort_text = LibraryModel::SortTextForArtist(s.artist()); + break; + + case LibraryModel::GroupBy_YearAlbum: + year = qMax(0, s.year()); + display_text = LibraryModel::PrettyYearAlbum(year, s.album()); + sort_text = LibraryModel::SortTextForYear(year) + s.album(); + break; + + case LibraryModel::GroupBy_Year: + year = qMax(0, s.year()); + display_text = QString::number(year); + sort_text = LibraryModel::SortTextForYear(year) + " "; + break; + + case LibraryModel::GroupBy_Composer: display_text = s.composer(); + case LibraryModel::GroupBy_Genre: if (display_text.isNull()) display_text = s.genre(); + case LibraryModel::GroupBy_Album: if (display_text.isNull()) display_text = s.album(); + case LibraryModel::GroupBy_AlbumArtist: if (display_text.isNull()) display_text = s.effective_albumartist(); + display_text = LibraryModel::TextOrUnknown(display_text); + sort_text = LibraryModel::SortTextForArtist(display_text); + break; + + case LibraryModel::GroupBy_FileType: + display_text = s.TextForFiletype(); + sort_text = display_text; + break; + + case LibraryModel::GroupBy_None: + return parent; + } + + // Find a container for this level + key->group_[level] = display_text; + QStandardItem* container = containers_[*key]; + if (!container) { + container = new QStandardItem(display_text); + container->setData(key->provider_index_, Role_ProviderIndex); + container->setData(sort_text, LibraryModel::Role_SortText); + container->setData(group_by_[level], LibraryModel::Role_ContainerType); + + parent->appendRow(container); + containers_[*key] = container; + } + + // Create the container for the next level. + return BuildContainers(s, container, key, level + 1); +} + +void GlobalSearchView::SwapModels() { + art_requests_.clear(); + + qSwap(front_model_, back_model_); + qSwap(front_proxy_, back_proxy_); + + ui_->results->setModel(front_proxy_); +} + +void GlobalSearchView::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 = front_proxy_->mapToSource(proxy_index); + front_model_->itemFromIndex(source_index)->setData(true, Role_LazyLoadingArt); + + const SearchProvider::Result result = + source_index.data(Role_Result).value(); + + int id = engine_->LoadArtAsync(result); + art_requests_[id] = source_index; +} + +void GlobalSearchView::ArtLoaded(int id, const QPixmap& pixmap) { + if (!art_requests_.contains(id)) + return; + QModelIndex index = art_requests_.take(id); + + front_model_->itemFromIndex(index)->setData(pixmap, Qt::DecorationRole); +} + +void GlobalSearchView::LoadTracks() { + QModelIndex index = ui_->results->currentIndex(); + if (!index.isValid()) + index = front_proxy_->index(0, 0); + + if (!index.isValid()) + return; + + const SearchProvider::Result result = + index.data(Role_Result).value(); + + engine_->LoadTracksAsync(result); +} + +void GlobalSearchView::TracksLoaded(int id, MimeData* mime_data) { + if (!mime_data) + return; + + mime_data->from_doubleclick_ = true; + emit AddToPlaylist(mime_data); +} diff --git a/src/globalsearch/globalsearchview.h b/src/globalsearch/globalsearchview.h new file mode 100644 index 000000000..4a564bcdc --- /dev/null +++ b/src/globalsearch/globalsearchview.h @@ -0,0 +1,133 @@ +/* This file is part of Clementine. + Copyright 2012, David Sansome + + 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 . +*/ + +#ifndef GLOBALSEARCHVIEW_H +#define GLOBALSEARCHVIEW_H + +#include "searchprovider.h" +#include "library/librarymodel.h" +#include "ui/settingsdialog.h" + +#include + +class Application; +class Ui_GlobalSearchView; + +class QMimeData; +class QSortFilterProxyModel; +class QStandardItem; +class QStandardItemModel; + +class GlobalSearchView : public QWidget { + Q_OBJECT + +public: + GlobalSearchView(Application* app, QWidget* parent = 0); + ~GlobalSearchView(); + + static const int kSwapModelsTimeoutMsec; + + enum Role { + Role_Result = LibraryModel::LastRole, + Role_LazyLoadingArt, + Role_ProviderIndex, + + LastRole + }; + + struct ContainerKey { + int provider_index_; + QString group_[3]; + }; + + // Called by the delegate + void LazyLoadArt(const QModelIndex& index); + +public slots: + void StartSearch(const QString& query); + +signals: + void AddToPlaylist(QMimeData* data); + void OpenSettingsAtPage(SettingsDialog::Page page); + +private slots: + void SwapModels(); + void TextEdited(const QString& text); + void AddResults(int id, const SearchProvider::ResultList& results); + + void ArtLoaded(int id, const QPixmap& pixmap); + void TracksLoaded(int id, MimeData* mime_data); + +private: + void LoadTracks(); + QStandardItem* BuildContainers(const Song& metadata, QStandardItem* parent, + ContainerKey* key, int level = 0); + +private: + Application* app_; + GlobalSearch* engine_; + Ui_GlobalSearchView* ui_; + + int last_search_id_; + + // 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_; + + QTimer* swap_models_timer_; + + LibraryModel::Grouping group_by_; + + QMap provider_sort_indices_; + int next_provider_sort_index_; + QMap containers_; + + QMap track_requests_; + QMap art_requests_; +}; + +inline uint qHash(const GlobalSearchView::ContainerKey& key) { + return qHash(key.provider_index_) + ^ qHash(key.group_[0]) + ^ qHash(key.group_[1]) + ^ qHash(key.group_[2]); +} + +inline bool operator <(const GlobalSearchView::ContainerKey& left, + const GlobalSearchView::ContainerKey& right) { + #define CMP(field) \ + if (left.field < right.field) return true; \ + if (left.field > right.field) return false + + CMP(provider_index_); + CMP(group_[0]); + CMP(group_[1]); + CMP(group_[2]); + return false; + + #undef CMP +} + +#endif // GLOBALSEARCHVIEW_H diff --git a/src/globalsearch/globalsearchwidget.ui b/src/globalsearch/globalsearchview.ui similarity index 55% rename from src/globalsearch/globalsearchwidget.ui rename to src/globalsearch/globalsearchview.ui index 2b2cc2cdf..1e196178b 100644 --- a/src/globalsearch/globalsearchwidget.ui +++ b/src/globalsearch/globalsearchview.ui @@ -1,40 +1,43 @@ - GlobalSearchWidget - + GlobalSearchView + 0 0 - 522 - 53 + 400 + 300 Form - + + + 0 + + + 0 + - - Search around all your sources (library, internet services, ...) - Search for anything - - - - 16 - 16 - + + + QAbstractItemView::NoEditTriggers - + true + + false + diff --git a/src/globalsearch/globalsearchwidget.cpp b/src/globalsearch/globalsearchwidget.cpp deleted file mode 100644 index d7e5e4be1..000000000 --- a/src/globalsearch/globalsearchwidget.cpp +++ /dev/null @@ -1,818 +0,0 @@ -/* This file is part of Clementine. - Copyright 2010, David Sansome - - 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 . -*/ - -#include "config.h" -#include "globalsearch.h" -#include "globalsearchitemdelegate.h" -#include "globalsearchsortmodel.h" -#include "globalsearchtooltip.h" -#include "globalsearchwidget.h" -#include "ui_globalsearchwidget.h" -#include "core/logging.h" -#include "core/stylesheetloader.h" -#include "core/utilities.h" -#include "playlist/playlistview.h" -#include "playlist/songmimedata.h" -#include "ui/qt_blurimage.h" -#include "widgets/stylehelper.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -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) - : QWidget(parent), - ui_(new Ui_GlobalSearchWidget), - engine_(NULL), - last_id_(0), - order_arrived_counter_(0), - closed_since_search_began_(false), - 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), - consume_focus_out_(false), - swap_models_timer_(new QTimer(this)), - background_(":allthethings.png"), - desktop_(qApp->desktop()), - show_tooltip_(true), - combine_identical_results_(true), - next_suggestion_timer_(new QTimer(this)) -{ - ui_->setupUi(this); - ReloadSettings(); - - // Set up the sorting proxy model - 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); - - combine_cache_[front_model_] = new CombineCache(front_model_); - combine_cache_[back_model_] = new CombineCache(back_model_); - - // Set up the popup - view_->setObjectName("popup"); - view_->setWindowFlags(Qt::Popup); - view_->setFocusPolicy(Qt::NoFocus); - view_->setFocusProxy(ui_->search); - view_->installEventFilter(this); - - view_->setModel(front_proxy_); - view_->setItemDelegate(new GlobalSearchItemDelegate(this)); - view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - view_->setEditTriggers(QAbstractItemView::NoEditTriggers); - - ui_->search->installEventFilter(this); - - // Actions - add_ = new QAction(tr("Add to playlist"), this); - add_and_play_ = new QAction(tr("Add and play now"), this); - add_and_queue_ = new QAction(tr("Queue track"), this); - replace_ = new QAction(tr("Replace current playlist"), this); - replace_and_play_ = new QAction(tr("Replace and play now"), this); - - add_->setShortcut(QKeySequence(Qt::Key_Return)); - add_and_play_->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Return)); - add_and_queue_->setShortcut(QKeySequence(Qt::SHIFT | Qt::Key_Return)); - replace_->setShortcut(QKeySequence(Qt::ALT | Qt::Key_Return)); - replace_and_play_->setShortcut(QKeySequence(Qt::ALT | Qt::CTRL | Qt::Key_Return)); - - connect(add_, SIGNAL(triggered()), SLOT(AddCurrent())); - connect(add_and_play_, SIGNAL(triggered()), SLOT(AddAndPlayCurrent())); - connect(add_and_queue_, SIGNAL(triggered()), SLOT(AddAndQueueCurrent())); - connect(replace_, SIGNAL(triggered()), SLOT(ReplaceCurrent())); - connect(replace_and_play_, SIGNAL(triggered()), SLOT(ReplaceAndPlayCurrent())); - - actions_ << add_ << add_and_play_ << add_and_queue_ << replace_ - << replace_and_play_; - - // Load style sheets - StyleSheetLoader* style_loader = new StyleSheetLoader(this); - style_loader->SetStyleSheet(this, ":globalsearch.css"); - - // Icons - ui_->settings->setIcon(IconLoader::Load("configure")); - - swap_models_timer_->setSingleShot(true); - swap_models_timer_->setInterval(kSwapModelsTimeoutMsec); - - next_suggestion_timer_->setInterval(kSuggestionTimeoutMsec); - hint_text_ = ui_->search->hint(); - - connect(ui_->search, SIGNAL(textChanged(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() { - delete ui_; - qDeleteAll(combine_cache_.values()); -} - -void GlobalSearchWidget::Init(GlobalSearch* engine) { - engine_ = engine; - - // These have to be queued connections because they may get emitted before - // our call to Search() (or whatever) returns and we add the ID to the map. - connect(engine_, SIGNAL(ResultsAvailable(int,SearchProvider::ResultList)), - SLOT(AddResults(int,SearchProvider::ResultList)), - Qt::QueuedConnection); - connect(engine_, SIGNAL(ArtLoaded(int,QPixmap)), SLOT(ArtLoaded(int,QPixmap)), - Qt::QueuedConnection); - connect(engine_, SIGNAL(TracksLoaded(int,MimeData*)), SLOT(TracksLoaded(int,MimeData*)), - Qt::QueuedConnection); - - view_->setStyle(new PlaylistProxyStyle(style())); - - // The style helper's base color doesn't get initialised until after the - // constructor. - QPalette view_palette = view_->palette(); - view_palette.setColor(QPalette::Text, Utils::StyleHelper::panelTextColor()); - view_palette.setColor(QPalette::HighlightedText, QColor(60, 60, 60)); - 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(ui_->settings->mapTo(this, ui_->settings->rect().center()).x() - - 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::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(); - closed_since_search_began_ = false; - - // Add results to the back model, switch models after some delay. - back_model_->clear(); - combine_cache_[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_); - // If text query is empty, don't start a new search - if (trimmed_text.isEmpty()) { - last_id_ = -1; - return; - } - last_id_ = engine_->SearchAsync(trimmed_text); -} - -void GlobalSearchWidget::SwapModels() { - art_requests_.clear(); - - qSwap(front_model_, back_model_); - qSwap(front_proxy_, back_proxy_); - - view_->setModel(front_proxy_); - connect(view_->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), - SLOT(UpdateTooltip())); - - if (!closed_since_search_began_) - RepositionPopup(); -} - -void GlobalSearchWidget::StartSearch(const QString& query) { - ui_->search->setText(query); - TextEdited(query); - - // Swap models immediately - swap_models_timer_->stop(); - SwapModels(); -} - -void GlobalSearchWidget::AddResults(int id, const SearchProvider::ResultList& results) { - if (id != last_id_) - return; - - foreach (const SearchProvider::Result& result, results) { - QStandardItem* item = new QStandardItem; - item->setData(QVariant::fromValue(result), Role_PrimaryResult); - item->setData(QVariant::fromValue(SearchProvider::ResultList() << result), Role_AllResults); - item->setData(order_arrived_counter_, Role_OrderArrived); - - QPixmap pixmap; - if (engine_->FindCachedPixmap(result, &pixmap)) { - item->setData(pixmap, Qt::DecorationRole); - } - - current_model_->appendRow(item); - - QModelIndex index = item->index(); - combine_cache_[current_model_]->Insert(index); - - if (combine_identical_results_) { - // Maybe we can combine this result with an identical result from another - // provider. - QModelIndexList candidates = combine_cache_[current_model_]->FindCandidates(index); - - foreach (const QModelIndex& candidate, candidates) { - if (!candidate.isValid()) - continue; - - CombineAction action = CanCombineResults(index, candidate); - - switch (action) { - case CannotCombine: - continue; - - case LeftPreferred: - CombineResults(index, candidate); - break; - - case RightPreferred: - CombineResults(candidate, index); - break; - } - - // We've just invalidated the indexes so we have to stop. - break; - } - } - } - - order_arrived_counter_ ++; - - if (!closed_since_search_began_) { - RepositionPopup(); - UpdateTooltipPosition(); - } -} - -void GlobalSearchWidget::RepositionPopup() { - if (front_model_->rowCount() == 0) { - HidePopup(false); - return; - } - - closed_since_search_began_ = false; - - int h = view_->sizeHintForRow(0) * float(0.5 + - qBound(kMinVisibleItems, front_model_->rowCount(), kMaxVisibleItems)); - int w = ui_->search->width(); - - const QPoint pos = ui_->search->mapToGlobal(ui_->search->rect().bottomLeft()); - - // Shrink the popup if it would otherwise go off the screen - const QRect screen = desktop_->availableGeometry(ui_->search); - h = qMin(h, screen.bottom() - pos.y()); - - view_->setGeometry(QRect(pos, QSize(w, h))); - - if (!view_->isVisible()) { - view_->show(); - ui_->search->setFocus(); - } -} - -bool GlobalSearchWidget::eventFilter(QObject* o, QEvent* e) { - if (o == ui_->search) - return EventFilterSearchWidget(o, e); - - if (o == view_) - return EventFilterPopup(o, e); - - return QWidget::eventFilter(o, e); -} - -bool GlobalSearchWidget::EventFilterSearchWidget(QObject* o, QEvent* e) { - switch (e->type()) { - case QEvent::FocusOut: - if (consume_focus_out_ && view_->isVisible()) - return true; - break; - - case QEvent::FocusIn: { - QFocusEvent* fe = static_cast(e); - if (fe->reason() == Qt::MouseFocusReason) { - RepositionPopup(); - } - break; - } - - case QEvent::KeyPress: { - QKeyEvent* ke = static_cast(e); - const int key = ke->key(); - - switch (key) { - case Qt::Key_Up: - case Qt::Key_Down: - case Qt::Key_PageUp: - case Qt::Key_PageDown: - // If we got one of these it means the popup wasn't visible, so show it - // now. - RepositionPopup(); - return true; - - case Qt::Key_Escape: - ui_->search->LineEditInterface::clear(); - return true; - - default: - break; - } - } - - default: - break; - } - - return QWidget::eventFilter(o, e); -} - -bool GlobalSearchWidget::EventFilterPopup(QObject*, QEvent* e) { - // Most of this is borrowed from QCompleter::eventFilter - - switch (e->type()) { - case QEvent::KeyPress: { - QKeyEvent* ke = static_cast(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(front_proxy_->index(front_proxy_->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(front_proxy_->index(0, 0)); - return true; - } else if (cur_index.row() == front_proxy_->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 - consume_focus_out_ = false; - (static_cast(ui_->search))->event(ke); - consume_focus_out_ = true; - - if (e->isAccepted() || !view_->isVisible()) { - // widget lost focus, hide the popup - if (!ui_->search->hasFocus()) - HidePopup(true); - 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: - // Handle the QActions here - they don't activate when the tooltip is showing - if (ke->modifiers() & Qt::AltModifier && ke->modifiers() & Qt::ControlModifier) - replace_and_play_->trigger(); - else if (ke->modifiers() & Qt::AltModifier) - replace_->trigger(); - else if (ke->modifiers() & Qt::ControlModifier) - add_and_play_->trigger(); - else if (ke->modifiers() & Qt::ShiftModifier) - add_and_queue_->trigger(); - else - add_->trigger(); - break; - - case Qt::Key_F4: - if (ke->modifiers() & Qt::AltModifier) - HidePopup(true); - break; - - case Qt::Key_Backtab: - case Qt::Key_Escape: - HidePopup(true); - break; - - default: - break; - } - - return true; - } - - case QEvent::MouseButtonPress: - if (!view_->underMouse()) { - HidePopup(true); - 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& proxy_index) { - if (!proxy_index.isValid() || proxy_index.data(Role_LazyLoadingArt).isValid()) { - return; - } - if (proxy_index.model() != front_proxy_) { - return; - } - - 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(); - - int id = engine_->LoadArtAsync(result); - art_requests_[id] = source_index; -} - -void GlobalSearchWidget::ArtLoaded(int id, const QPixmap& pixmap) { - if (!art_requests_.contains(id)) - return; - QModelIndex index = art_requests_.take(id); - - front_model_->itemFromIndex(index)->setData(pixmap, Qt::DecorationRole); -} - -void GlobalSearchWidget::ResultDoubleClicked() { - LoadTracks(NULL); -} - -void GlobalSearchWidget::AddCurrent() { - LoadTracks(add_); -} - -void GlobalSearchWidget::AddAndPlayCurrent() { - LoadTracks(add_and_play_); -} - -void GlobalSearchWidget::AddAndQueueCurrent() { - LoadTracks(add_and_queue_); -} - -void GlobalSearchWidget::ReplaceCurrent() { - LoadTracks(replace_); -} - -void GlobalSearchWidget::ReplaceAndPlayCurrent() { - LoadTracks(replace_and_play_); -} - -void GlobalSearchWidget::LoadTracks(QAction* trigger) { - QModelIndex index = view_->currentIndex(); - if (!index.isValid()) - index = front_proxy_->index(0, 0); - - if (!index.isValid()) - return; - - int result_index = 0; - if (tooltip_ && tooltip_->isVisible()) { - result_index = tooltip_->ActiveResultIndex(); - } - - const SearchProvider::ResultList results = - index.data(Role_AllResults).value(); - - if (result_index < 0 || result_index >= results.count()) - return; - - int id = engine_->LoadTracksAsync(results[result_index]); - track_requests_[id] = trigger; -} - -void GlobalSearchWidget::TracksLoaded(int id, MimeData* mime_data) { - if (!track_requests_.contains(id)) - return; - - QAction* trigger = track_requests_.take(id); - - if (!mime_data) - return; - - if (trigger == NULL) { - mime_data->from_doubleclick_ = true; - } else { - if (trigger == add_and_play_) { - mime_data->override_user_settings_ = true; - mime_data->play_now_ = true; - } else if (trigger == add_and_queue_) { - mime_data->enqueue_now_ = true; - } else if (trigger == replace_) { - mime_data->clear_first_= true; - } else if (trigger == replace_and_play_) { - mime_data->clear_first_ = true; - mime_data->override_user_settings_ = true; - mime_data->play_now_ = true; - } - } - - emit AddToPlaylist(mime_data); -} - -void GlobalSearchWidget::ReloadSettings() { - QSettings s; - s.beginGroup(GlobalSearch::kSettingsGroup); - - show_tooltip_ = s.value("tooltip", true).toBool(); - combine_identical_results_ = s.value("combine_identical_results", true).toBool(); - provider_order_ = s.value("provider_order", QStringList() << "library").toStringList(); - setVisible(s.value("show_globalsearch", true).toBool()); - - if (tooltip_) { - tooltip_->ReloadSettings(); - } - -} - -GlobalSearchWidget::CombineAction GlobalSearchWidget::CanCombineResults( - const QModelIndex& left, const QModelIndex& right) const { - const SearchProvider::Result r1 = left.data(Role_PrimaryResult) - .value(); - const SearchProvider::Result r2 = right.data(Role_PrimaryResult) - .value(); - - // If you change the logic here remember to change CombineCache::Hash too. - - if (r1.match_quality_ != r2.match_quality_ || r1.type_ != r2.type_) - return CannotCombine; - -#define StringsDiffer(field) \ - (QString::compare(r1.metadata_.field(), r2.metadata_.field(), Qt::CaseInsensitive) != 0) - - switch (r1.type_) { - case globalsearch::Type_Track: - if (StringsDiffer(title)) - return CannotCombine; - // fallthrough - case globalsearch::Type_Album: - if (StringsDiffer(album) || StringsDiffer(artist)) - return CannotCombine; - break; - case globalsearch::Type_Stream: - if (StringsDiffer(url().toString)) - return CannotCombine; - break; - } - -#undef StringsDiffer - - // They look the same - decide which provider we like best. - const int p1 = provider_order_.indexOf(r1.provider_->id()); - const int p2 = provider_order_.indexOf(r2.provider_->id()); - - return p2 > p1 ? LeftPreferred : RightPreferred; -} - -void GlobalSearchWidget::CombineResults(const QModelIndex& superior, const QModelIndex& inferior) { - QStandardItem* superior_item = current_model_->itemFromIndex(superior); - QStandardItem* inferior_item = current_model_->itemFromIndex(inferior); - - SearchProvider::ResultList superior_results = - superior_item->data(Role_AllResults).value(); - SearchProvider::ResultList inferior_results = - inferior_item->data(Role_AllResults).value(); - - superior_results.append(inferior_results); - superior_item->setData(QVariant::fromValue(superior_results), Role_AllResults); - - combine_cache_[current_model_]->Remove(inferior_item->index()); - current_model_->invisibleRootItem()->removeRow(inferior_item->row()); -} - -void GlobalSearchWidget::HidePopup(bool manual) { - if (manual) { - closed_since_search_began_ = true; - } - - if (tooltip_) - tooltip_->hide(); - view_->hide(); -} - -void GlobalSearchWidget::UpdateTooltip() { - if (!view_->isVisible() || !show_tooltip_) { - if (tooltip_) - tooltip_->hide(); - return; - } - - const QModelIndex current = view_->selectionModel()->currentIndex(); - if (!current.isValid()) - return; - - const SearchProvider::ResultList results = current.data(Role_AllResults) - .value(); - - if (!tooltip_) { - tooltip_.reset(new GlobalSearchTooltip(view_)); - tooltip_->setFont(view_->font()); - tooltip_->setPalette(view_->palette()); - tooltip_->SetActions(actions_); - } - - tooltip_->SetResults(results); - UpdateTooltipPosition(); -} - -void GlobalSearchWidget::UpdateTooltipPosition() { - if (!tooltip_ || !view_->isVisible()) - return; - - const QModelIndex current = view_->selectionModel()->currentIndex(); - if (!current.isValid()) { - tooltip_->hide(); - return; - } - - const QRect item_rect = view_->visualRect(current); - const QPoint popup_pos = item_rect.topRight() + - QPoint(-GlobalSearchTooltip::kArrowWidth, - item_rect.height() / 2); - - tooltip_->ShowAt(view_->mapToGlobal(popup_pos)); -} - -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 += QString(", %1 ").arg(tr("e.g.")) + suggestions.join(", "); - } - - ui_->search->set_hint(hint); -} - -GlobalSearchWidget::CombineCache::CombineCache(QAbstractItemModel* model) - : model_(model) -{ -} - -uint GlobalSearchWidget::CombineCache::Hash(const QModelIndex& index) { - const SearchProvider::Result r = index.data(Role_PrimaryResult) - .value(); - - uint ret = qHash(r.match_quality_) ^ qHash(r.type_); - - switch (r.type_) { - case globalsearch::Type_Track: - ret ^= qHash(r.metadata_.title()); - // fallthrough - case globalsearch::Type_Album: - ret ^= qHash(r.metadata_.album()); - ret ^= qHash(r.metadata_.artist()); - break; - - case globalsearch::Type_Stream: - ret ^= qHash(r.metadata_.url().toString()); - break; - } - - return ret; -} - -void GlobalSearchWidget::CombineCache::Insert(const QModelIndex& index) { - data_.insert(Hash(index), index.row()); -} - -void GlobalSearchWidget::CombineCache::Remove(const QModelIndex& index) { - // This is really inefficient but we're not doing it much - find any items - // with a row greater than this one and shuffle them down one. - - for (QMultiMap::iterator it = data_.begin() ; it != data_.end() ; ++it) { - if (it.value() > index.row()) - (*it) --; - } - - // Now remove the row itself. - QMultiMap::iterator it = data_.find(Hash(index), index.row()); - if (it != data_.end()) - data_.erase(it); -} - -QModelIndexList GlobalSearchWidget::CombineCache::FindCandidates( - const QModelIndex& result) const { - QModelIndexList ret; - foreach (int row, data_.values(Hash(result))) { - if (row != result.row()) { - ret << model_->index(row, 0); - } - } - - return ret; -} - -void GlobalSearchWidget::CombineCache::Clear() { - data_.clear(); -} diff --git a/src/globalsearch/globalsearchwidget.h b/src/globalsearch/globalsearchwidget.h deleted file mode 100644 index dffdd9b29..000000000 --- a/src/globalsearch/globalsearchwidget.h +++ /dev/null @@ -1,194 +0,0 @@ -/* This file is part of Clementine. - Copyright 2010, David Sansome - - 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 . -*/ - -#ifndef GLOBALSEARCHWIDGET_H -#define GLOBALSEARCHWIDGET_H - -#include "searchprovider.h" -#include "ui/settingsdialog.h" - -#include -#include - -#include - -class GlobalSearch; -class GlobalSearchTooltip; -class LibraryBackendInterface; -class Ui_GlobalSearchWidget; - -class QDesktopWidget; -class QListView; -class QMimeData; -class QModelIndex; -class QSortFilterProxyModel; -class QStandardItemModel; -class QToolButton; - - -class GlobalSearchWidget : public QWidget { - Q_OBJECT - -public: - GlobalSearchWidget(QWidget* parent = 0); - ~GlobalSearchWidget(); - - 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, - Role_AllResults, - Role_LazyLoadingArt, - Role_OrderArrived - }; - - void Init(GlobalSearch* engine_); - - // Called by the delegate - void LazyLoadArt(const QModelIndex& index); - - // QObject - bool eventFilter(QObject* o, QEvent* e); - -public slots: - void ReloadSettings(); - void StartSearch(const QString& query); - -signals: - void AddToPlaylist(QMimeData* data); - void OpenSettingsAtPage(SettingsDialog::Page page); - -protected: - void resizeEvent(QResizeEvent* e); - void paintEvent(QPaintEvent* e); - void showEvent(QShowEvent* e); - void hideEvent(QHideEvent* e); - -private slots: - void TextEdited(const QString& text); - void AddResults(int id, const SearchProvider::ResultList& results); - - void ArtLoaded(int id, const QPixmap& pixmap); - - void TracksLoaded(int id, MimeData* mime_data); - - void ResultDoubleClicked(); - void AddCurrent(); - void AddAndPlayCurrent(); - void AddAndQueueCurrent(); - void ReplaceCurrent(); - void ReplaceAndPlayCurrent(); - void SettingsClicked(); - - void HidePopup(bool manual); - void UpdateTooltip(); - void UpdateTooltipPosition(); - - void SwapModels(); - - void NextSuggestion(); - -private: - // Return values from CanCombineResults - enum CombineAction { - CannotCombine, // The two results are different and can't be combined - LeftPreferred, // The two results can be combined - the left one is better - RightPreferred // The two results can be combined - the right one is better - }; - - class CombineCache { - public: - CombineCache(QAbstractItemModel* model); - - QModelIndexList FindCandidates(const QModelIndex& result) const; - void Insert(const QModelIndex& index); - void Remove(const QModelIndex& index); - void Clear(); - - static uint Hash(const QModelIndex& index); - - private: - QAbstractItemModel* model_; - QMultiMap data_; - }; - - void RepositionPopup(); - CombineAction CanCombineResults(const QModelIndex& left, const QModelIndex& right) const; - void CombineResults(const QModelIndex& superior, const QModelIndex& inferior); - - bool EventFilterSearchWidget(QObject* o, QEvent* e); - bool EventFilterPopup(QObject* o, QEvent* e); - - void LoadTracks(QAction* trigger); - -private: - Ui_GlobalSearchWidget* ui_; - - GlobalSearch* engine_; - int last_id_; - int order_arrived_counter_; - bool closed_since_search_began_; - - QMap art_requests_; - QMap track_requests_; - - // 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_; - - QMap combine_cache_; - - QSortFilterProxyModel* front_proxy_; - QSortFilterProxyModel* back_proxy_; - QSortFilterProxyModel* current_proxy_; - - QListView* view_; - bool consume_focus_out_; - - QTimer* swap_models_timer_; - - QPixmap background_; - QPixmap background_scaled_; - - QDesktopWidget* desktop_; - - bool show_tooltip_; - bool combine_identical_results_; - QStringList provider_order_; - - QScopedPointer tooltip_; - - QAction* add_; - QAction* add_and_play_; - QAction* add_and_queue_; - QAction* replace_; - QAction* replace_and_play_; - QList actions_; - - QString hint_text_; - QTimer* next_suggestion_timer_; -}; - -#endif // GLOBALSEARCHWIDGET_H diff --git a/src/globalsearch/groovesharksearchprovider.cpp b/src/globalsearch/groovesharksearchprovider.cpp index 0120e13d6..849ce2493 100644 --- a/src/globalsearch/groovesharksearchprovider.cpp +++ b/src/globalsearch/groovesharksearchprovider.cpp @@ -66,15 +66,10 @@ void GroovesharkSearchProvider::SearchDone(int id, const SongList& songs) { const PendingState state = pending_searches_.take(id); const int global_search_id = state.orig_id_; - SongList songs_copy(songs); - SortSongs(&songs_copy); - ResultList ret; - foreach (const Song& song, songs_copy) { + foreach (const Song& song, songs) { Result result(this); - result.type_ = globalsearch::Type_Track; result.metadata_ = song; - result.match_quality_ = MatchQuality(state.tokens_, song.title()); ret << result; } @@ -119,31 +114,10 @@ void GroovesharkSearchProvider::AlbumArtLoaded(quint64 id, const QImage& image) } void GroovesharkSearchProvider::LoadTracksAsync(int id, const Result& result) { - SongList ret; - - switch (result.type_) { - case globalsearch::Type_Track: { - ret << result.metadata_; - SortSongs(&ret); - - InternetSongMimeData* mime_data = new InternetSongMimeData(service_); - mime_data->songs = ret; - - emit TracksLoaded(id, mime_data); - break; - } - - case globalsearch::Type_Album: { - InternetSongMimeData* mime_data = new InternetSongMimeData(service_); - mime_data->songs = result.album_songs_; - emit TracksLoaded(id, mime_data); - break; - } - - default: - Q_ASSERT(0); - } + InternetSongMimeData* mime_data = new InternetSongMimeData(service_); + mime_data->songs << result.metadata_; + emit TracksLoaded(id, mime_data); } bool GroovesharkSearchProvider::IsLoggedIn() { @@ -158,24 +132,12 @@ void GroovesharkSearchProvider::AlbumSongsLoaded(quint64 id, const SongList& son const PendingState state = pending_searches_.take(id); const int global_search_id = state.orig_id_; ResultList ret; - if (!songs.isEmpty()) { + foreach (const Song& s, songs) { Result result(this); - result.type_ = globalsearch::Type_Album; - const QString& artist = songs.last().artist(); - const QString& album = songs.last().album(); - result.metadata_.set_album(album); - result.metadata_.set_artist(artist); - result.metadata_.set_art_automatic(songs.last().art_automatic()); - result.match_quality_ = - qMin(MatchQuality(state.tokens_, album), - MatchQuality(state.tokens_, artist)); - foreach (const Song& s, songs) { - result.album_songs_ << s; - } - result.album_size_ = result.album_songs_.size(); - + result.metadata_ = s; ret << result; } + emit ResultsAvailable(global_search_id, ret); MaybeSearchFinished(global_search_id); } diff --git a/src/globalsearch/icecastsearchprovider.cpp b/src/globalsearch/icecastsearchprovider.cpp index e55527a4d..ace22a63e 100644 --- a/src/globalsearch/icecastsearchprovider.cpp +++ b/src/globalsearch/icecastsearchprovider.cpp @@ -30,16 +30,13 @@ SearchProvider::ResultList IcecastSearchProvider::Search(int id, const QString& IcecastBackend::StationList stations = backend_->GetStations(query); ResultList ret; - const QStringList tokens = TokenizeQuery(query); - foreach (const IcecastBackend::Station& station, stations) { if (ret.count() > 3) break; Result result(this); - result.type_ = globalsearch::Type_Stream; + result.group_automatically_ = false; result.metadata_ = station.ToSong(); - result.match_quality_ = MatchQuality(tokens, station.name); ret << result; } diff --git a/src/globalsearch/librarysearchprovider.cpp b/src/globalsearch/librarysearchprovider.cpp index 06db6cb67..493c07fb5 100644 --- a/src/globalsearch/librarysearchprovider.cpp +++ b/src/globalsearch/librarysearchprovider.cpp @@ -23,6 +23,8 @@ #include "library/sqlrow.h" #include "playlist/songmimedata.h" +#include + LibrarySearchProvider::LibrarySearchProvider(LibraryBackendInterface* backend, const QString& name, @@ -55,58 +57,11 @@ SearchProvider::ResultList LibrarySearchProvider::Search(int id, const QString& return ResultList(); } - const QStringList tokens = TokenizeQuery(query); - - QMultiMap albums; - QSet albums_with_non_track_matches; - + // Build the result list 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()); - } - - globalsearch::MatchQuality quality = MatchQuality(tokens, song.title()); - - if (quality != globalsearch::Quality_None) { - // If the query matched in the song title then we're interested in this - // as an individual song. - Result result(this); - result.type_ = globalsearch::Type_Track; - result.metadata_ = song; - result.match_quality_ = quality; - 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_ = globalsearch::Type_Album; - result.metadata_ = albums.value(key); - result.album_size_ = albums.count(key); - result.match_quality_ = - qMin( - MatchQuality(tokens, result.metadata_.albumartist()), - qMin(MatchQuality(tokens, result.metadata_.artist()), - MatchQuality(tokens, result.metadata_.album()))); - - result.album_songs_ = albums.values(key); - SortSongs(&result.album_songs_); - + result.metadata_.InitFromQuery(q, true); ret << result; } @@ -114,44 +69,9 @@ SearchProvider::ResultList LibrarySearchProvider::Search(int id, const QString& } void LibrarySearchProvider::LoadTracksAsync(int id, const Result& result) { - SongList ret; - - switch (result.type_) { - case globalsearch::Type_Track: - // This is really easy - we just emit the track again. - ret << result.metadata_; - break; - - case globalsearch::Type_Album: { - // Find all the songs in this album. - LibraryQuery query; - query.SetColumnSpec("ROWID, " + Song::kColumnSpec); - query.AddCompilationRequirement(result.metadata_.is_compilation()); - query.AddWhere("album", result.metadata_.album()); - - if (!result.metadata_.is_compilation()) - query.AddWhere("artist", result.metadata_.artist()); - - if (!backend_->ExecQuery(&query)) { - break; - } - - while (query.Next()) { - Song song; - song.InitFromQuery(query, true); - ret << song; - } - } - - default: - break; - } - - SortSongs(&ret); - SongMimeData* mime_data = new SongMimeData; mime_data->backend = backend_; - mime_data->songs = ret; + mime_data->songs = SongList() << result.metadata_; emit TracksLoaded(id, mime_data); } diff --git a/src/globalsearch/searchprovider.cpp b/src/globalsearch/searchprovider.cpp index f06ea2187..a18f3aa6c 100644 --- a/src/globalsearch/searchprovider.cpp +++ b/src/globalsearch/searchprovider.cpp @@ -57,20 +57,14 @@ QStringList SearchProvider::TokenizeQuery(const QString& query) { return tokens; } -globalsearch::MatchQuality SearchProvider::MatchQuality( - const QStringList& tokens, const QString& string) { - globalsearch::MatchQuality ret = globalsearch::Quality_None; - +bool SearchProvider::Matches(const QStringList& tokens, const QString& string) { foreach (const QString& token, tokens) { - const int index = string.indexOf(token, 0, Qt::CaseInsensitive); - if (index == 0) { - return globalsearch::Quality_AtStart; - } else if (index != -1) { - ret = globalsearch::Quality_Middle; + if (!string.contains(token, Qt::CaseInsensitive)) { + return false; } } - return ret; + return true; } BlockingSearchProvider::BlockingSearchProvider(Application* app, QObject* parent) @@ -125,21 +119,6 @@ QImage SearchProvider::ScaleAndPad(const QImage& image) { return padded_image; } -namespace { - bool SortSongsCompare(const Song& left, const Song& right) { - if (left.disc() < right.disc()) - return true; - if (left.disc() > right.disc()) - return false; - - return left.track() < right.track(); - } -} - -void SearchProvider::SortSongs(SongList* list) { - qStableSort(list->begin(), list->end(), SortSongsCompare); -} - void SearchProvider::LoadArtAsync(int id, const Result& result) { emit ArtLoaded(id, QImage()); } diff --git a/src/globalsearch/searchprovider.h b/src/globalsearch/searchprovider.h index 1e43c035a..4e25eafed 100644 --- a/src/globalsearch/searchprovider.h +++ b/src/globalsearch/searchprovider.h @@ -23,7 +23,6 @@ #include #include "core/song.h" -#include "globalsearch/common.h" class Application; class MimeData; @@ -39,23 +38,19 @@ public: struct Result { Result(SearchProvider* provider = 0) - : provider_(provider), album_size_(0) {} + : provider_(provider), group_automatically_(true) {} // This must be set by the provider using the constructor. SearchProvider* provider_; - // These must be set explicitly by the provider. - globalsearch::Type type_; - globalsearch::MatchQuality match_quality_; + // If this is set to true, the view will group this result into + // artist/album categories as appropriate. + bool group_automatically_; + + // Must be set by the provider. Song metadata_; - // How many songs in the album - valid only if type == Type_Album. - int album_size_; - - // Songs in the album - valid only if type == Type_Album. This is only - // used for display in the tooltip, so it's fine not to provide it. - SongList album_songs_; - + // This is set and used by the GlobalSearch engine itself. QString pixmap_cache_key_; }; typedef QList ResultList; @@ -85,7 +80,7 @@ public: ArtIsInSongMetadata = 0x08, // 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 + // ShowConfig. If this is not set then the button will be greyed out // in the GUI. CanShowConfig = 0x10, @@ -151,10 +146,7 @@ protected: // 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 globalsearch::MatchQuality MatchQuality(const QStringList& tokens, const QString& string); - - // Sorts a list of songs by disc, then by track. - static void SortSongs(SongList* list); + static bool Matches(const QStringList& tokens, const QString& string); // Subclasses must call this from their constructors. void Init(const QString& name, const QString& id, const QIcon& icon, diff --git a/src/globalsearch/simplesearchprovider.cpp b/src/globalsearch/simplesearchprovider.cpp index 6e1bb39ac..05d79842d 100644 --- a/src/globalsearch/simplesearchprovider.cpp +++ b/src/globalsearch/simplesearchprovider.cpp @@ -68,37 +68,18 @@ SearchProvider::ResultList SimpleSearchProvider::Search(int id, const QString& q QMutexLocker l(&items_mutex_); foreach (const Item& item, items_) { Result result(this); - result.type_ = globalsearch::Type_Stream; - result.match_quality_ = globalsearch::Quality_None; + bool matched = true; foreach (const QString& token, tokens) { - if (item.keyword_.startsWith(token, Qt::CaseInsensitive)) { - result.match_quality_ = globalsearch::Quality_AtStart; - continue; - } - - if (!item.metadata_.title().contains(token, Qt::CaseInsensitive)) { - bool matched_safe_word = false; - foreach (const QString& safe_word, safe_words_) { - if (safe_word.startsWith(token, Qt::CaseInsensitive)) { - matched_safe_word = true; - break; - } - } - - if (matched_safe_word) - continue; - result.match_quality_ = globalsearch::Quality_None; + if (!item.keyword_.contains(token, Qt::CaseInsensitive) && + !safe_words_.contains(token, Qt::CaseInsensitive)) { + matched = false; break; } - - result.match_quality_ = qMin(result.match_quality_, globalsearch::Quality_Middle); } - if (result.match_quality_ == globalsearch::Quality_Middle) { - result.match_quality_ = MatchQuality(tokens, item.metadata_.title()); - } - if (result.match_quality_ != globalsearch::Quality_None) { + if (matched) { + result.group_automatically_ = false; result.metadata_ = item.metadata_; ret << result; } diff --git a/src/globalsearch/spotifysearchprovider.cpp b/src/globalsearch/spotifysearchprovider.cpp index c4402dc0c..c889c05da 100644 --- a/src/globalsearch/spotifysearchprovider.cpp +++ b/src/globalsearch/spotifysearchprovider.cpp @@ -88,9 +88,7 @@ void SpotifySearchProvider::SearchFinishedSlot(const pb::spotify::SearchResponse const pb::spotify::Track& track = response.result(i); Result result(this); - result.type_ = globalsearch::Type_Track; SpotifyService::SongFromProtobuf(track, &result.metadata_); - result.match_quality_ = MatchQuality(state.tokens_, result.metadata_.title()); ret << result; } @@ -98,21 +96,11 @@ void SpotifySearchProvider::SearchFinishedSlot(const pb::spotify::SearchResponse for (int i=0 ; isongs = SongList() << result.metadata_; - emit TracksLoaded(id, mime_data); - break; - } - - case globalsearch::Type_Album: { - SpotifyServer* s = server(); - if (!s) { - emit TracksLoaded(id, NULL); - return; - } - - QString uri = result.metadata_.url().toString(); - - pending_tracks_[uri] = id; - s->AlbumBrowse(uri); - break; - } - - default: - break; - } + SongMimeData* mime_data = new SongMimeData; + mime_data->songs << result.metadata_; + emit TracksLoaded(id, mime_data); } void SpotifySearchProvider::AlbumBrowseResponse(const pb::spotify::BrowseAlbumResponse& response) { diff --git a/src/globalsearch/tooltipactionwidget.cpp b/src/globalsearch/tooltipactionwidget.cpp deleted file mode 100644 index e9d074620..000000000 --- a/src/globalsearch/tooltipactionwidget.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/* This file is part of Clementine. - Copyright 2010, David Sansome - - 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 . -*/ - -#include "tooltipactionwidget.h" -#include "core/logging.h" - -#include -#include -#include - -const int TooltipActionWidget::kBorder = 16; -const int TooltipActionWidget::kSpacing = 6; -const int TooltipActionWidget::kTopPadding = 3; -const int TooltipActionWidget::kFadeDurationMsec = 200; - -TooltipActionWidget::TooltipActionWidget(QWidget* parent) - : QWidget(parent), - kTextHeight(fontMetrics().height()), - shortcut_width_(0), - description_width_(0) -{ - setMouseTracking(true); -} - -void TooltipActionWidget::SetActions(QList actions) { - actions_ = actions; - action_opacities_.clear(); - - int h = kTopPadding + kTextHeight * actions.count(); - shortcut_width_ = 0; - description_width_ = 0; - - foreach (const QAction* action, actions) { - shortcut_width_ = - qMax(shortcut_width_, - fontMetrics().width(action->shortcut().toString(QKeySequence::NativeText))); - description_width_ = - qMax(description_width_, fontMetrics().width(action->text())); - - QTimeLine* timeline = new QTimeLine(kFadeDurationMsec, this); - connect(timeline, SIGNAL(valueChanged(qreal)), SLOT(update())); - action_opacities_ << timeline; - } - - size_hint_ = QSize( - kBorder*2 + shortcut_width_ + kSpacing + description_width_, h); - - updateGeometry(); - update(); -} - -void TooltipActionWidget::paintEvent(QPaintEvent*) { - int y = kTopPadding; - - QPainter p(this); - p.setPen(palette().color(QPalette::Text)); - - for (int i=0 ; icurrentValue(); - const qreal description_opacity = 0.7 + 0.3 * timeline->currentValue(); - - p.setOpacity(shortcut_opacity); - p.drawText(shortcut_rect, Qt::AlignRight | Qt::AlignVCenter, - action->shortcut().toString(QKeySequence::NativeText)); - - p.setOpacity(description_opacity); - p.drawText(description_rect, Qt::AlignVCenter, action->text()); - - y += kTextHeight; - } -} - -int TooltipActionWidget::ActionAt(const QPoint& pos) const { - return (pos.y() - kTopPadding) / kTextHeight; -} - -void TooltipActionWidget::mouseMoveEvent(QMouseEvent* e) { - const int action = ActionAt(e->pos()); - - for (int i=0 ; idirection() != direction) { - timeline->setDirection(direction); - if (timeline->state() != QTimeLine::Running) - timeline->resume(); - } -} - -void TooltipActionWidget::mousePressEvent(QMouseEvent* e) { - const int action = ActionAt(e->pos()); - if (action >= 0 && action < actions_.count()) { - actions_[action]->trigger(); - } -} diff --git a/src/globalsearch/tooltipactionwidget.h b/src/globalsearch/tooltipactionwidget.h deleted file mode 100644 index 448680cbc..000000000 --- a/src/globalsearch/tooltipactionwidget.h +++ /dev/null @@ -1,61 +0,0 @@ -/* This file is part of Clementine. - Copyright 2010, David Sansome - - 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 . -*/ - -#ifndef TOOLTIPACTIONWIDGET_H -#define TOOLTIPACTIONWIDGET_H - -#include -#include - - -class TooltipActionWidget : public QWidget { - Q_OBJECT - -public: - TooltipActionWidget(QWidget* parent = 0); - - static const int kBorder; - static const int kSpacing; - static const int kTopPadding; - static const int kFadeDurationMsec; - - void SetActions(QList actions); - - QSize sizeHint() const { return size_hint_; } - -protected: - void paintEvent(QPaintEvent*); - void mouseMoveEvent(QMouseEvent* e); - void leaveEvent(QEvent* e); - void mousePressEvent(QMouseEvent* e); - -private: - int ActionAt(const QPoint& pos) const; - void StartAnimation(int i, QTimeLine::Direction direction); - -private: - const int kTextHeight; - - QList actions_; - QList action_opacities_; - - QSize size_hint_; - int shortcut_width_; - int description_width_; -}; - -#endif // TOOLTIPACTIONWIDGET_H diff --git a/src/globalsearch/tooltipresultwidget.cpp b/src/globalsearch/tooltipresultwidget.cpp deleted file mode 100644 index d120eff16..000000000 --- a/src/globalsearch/tooltipresultwidget.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/* This file is part of Clementine. - Copyright 2010, David Sansome - - 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 . -*/ - -#include "tooltipresultwidget.h" -#include "core/logging.h" - -#include - -const int TooltipResultWidget::kBorder = 15; -const int TooltipResultWidget::kSpacing = 3; -const int TooltipResultWidget::kTrackNumSpacing = 6; -const int TooltipResultWidget::kLineHeight = 1; -const int TooltipResultWidget::kIconSize = 16; - -TooltipResultWidget::TooltipResultWidget(const SearchProvider::Result& result, - QWidget* parent) - : QAbstractButton(parent), - result_(result), - kTextHeight(fontMetrics().height()), - kTrackNoWidth(fontMetrics().width("0000")), - bold_metrics_(fontMetrics()) -{ - bold_font_ = font(); - bold_font_.setBold(true); - bold_metrics_ = QFontMetrics(bold_font_); - - size_hint_ = CalculateSizeHint(); - - setCheckable(true); - setAutoExclusive(true); -} - -QSize TooltipResultWidget::sizeHint() const { - return size_hint_; -} - -QSize TooltipResultWidget::CalculateSizeHint() const { - int w = 0; - int h = 0; - - // Title text - h += kSpacing + kIconSize + kSpacing + kLineHeight; - w = qMax(w, kBorder + kTrackNoWidth + kTrackNumSpacing + - bold_metrics_.width(TitleText()) + kBorder); - - switch (result_.type_) { - case globalsearch::Type_Track: - case globalsearch::Type_Stream: - break; - - case globalsearch::Type_Album: - if (result_.album_songs_.isEmpty()) - break; - - // Song list - h += kSpacing + kSpacing * (result_.album_songs_.count() - 1) + - kTextHeight * result_.album_songs_.count(); - foreach (const Song& song, result_.album_songs_) { - w = qMax(w, kBorder + kTrackNoWidth + kTrackNumSpacing + - fontMetrics().width(song.TitleWithCompilationArtist()) + - kBorder); - } - - h += kSpacing + kLineHeight; - - break; - } - - return QSize(w, h); -} - -QString TooltipResultWidget::TitleText() const { - return result_.provider_->name(); -} - -void TooltipResultWidget::paintEvent(QPaintEvent*) { - QPainter p(this); - - const QColor text_color = palette().color(QPalette::BrightText); - - const qreal line_opacity = 0.1 + (isChecked() ? 0.2 : 0.0); - const qreal track_opacity = 0.1 + (isChecked() ? 0.5 : 0.0); - const qreal text_opacity = 0.4 + (isChecked() ? 0.5 : 0.0); - - int y = kSpacing; - - // Title text - QRect text_rect(kBorder + kTrackNoWidth + kTrackNumSpacing, y, - width() - kBorder*2 - kTrackNoWidth - kTrackNumSpacing, kIconSize); - p.setFont(bold_font_); - p.setPen(text_color); - p.setOpacity(text_opacity); - p.drawText(text_rect, Qt::AlignVCenter, TitleText()); - - // Title icon - QRect icon_rect(text_rect.left() - kTrackNumSpacing - kIconSize, y, - kIconSize, kIconSize); - p.drawPixmap(icon_rect, result_.provider_->icon().pixmap(kIconSize)); - - // Line - y += kIconSize + kSpacing; - p.setOpacity(line_opacity); - p.setPen(text_color); - p.drawLine(0, y, width(), y); - y += kLineHeight; - - switch (result_.type_) { - case globalsearch::Type_Track: - case globalsearch::Type_Stream: - break; - - case globalsearch::Type_Album: - if (result_.album_songs_.isEmpty()) - break; - - // Song list - y += kSpacing; - - p.setFont(font()); - - foreach (const Song& song, result_.album_songs_) { - QRect number_rect(kBorder, y, kTrackNoWidth, kTextHeight); - if (song.track() > 0) { - // Track number - p.setOpacity(track_opacity); - p.drawText(number_rect, Qt::AlignRight | Qt::AlignHCenter, - QString::number(song.track())); - } - - // Song title - QRect title_rect(number_rect.right() + kTrackNumSpacing, y, - width() - number_rect.right() - kTrackNumSpacing - kBorder, - kTextHeight); - p.setOpacity(text_opacity); - p.drawText(title_rect, song.TitleWithCompilationArtist()); - - y += kTextHeight + kSpacing; - } - - // Line - p.setOpacity(line_opacity); - p.drawLine(0, y, width(), y); - y += kLineHeight; - - break; - } - - y += kSpacing; -} diff --git a/src/globalsearch/tooltipresultwidget.h b/src/globalsearch/tooltipresultwidget.h deleted file mode 100644 index dd48aa5cb..000000000 --- a/src/globalsearch/tooltipresultwidget.h +++ /dev/null @@ -1,58 +0,0 @@ -/* This file is part of Clementine. - Copyright 2010, David Sansome - - 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 . -*/ - -#ifndef TOOLTIPRESULTWIDGET_H -#define TOOLTIPRESULTWIDGET_H - -#include "searchprovider.h" - -#include - -class TooltipResultWidget : public QAbstractButton { - Q_OBJECT - -public: - TooltipResultWidget(const SearchProvider::Result& result, QWidget* parent = 0); - - static const int kBorder; - static const int kSpacing; - static const int kTrackNumSpacing; - static const int kLineHeight; - static const int kIconSize; - - QSize sizeHint() const; - - QString TitleText() const; - -protected: - void paintEvent(QPaintEvent*); - -private: - QSize CalculateSizeHint() const; - -private: - SearchProvider::Result result_; - const int kTextHeight; - const int kTrackNoWidth; - - QSize size_hint_; - - QFont bold_font_; - QFontMetrics bold_metrics_; -}; - -#endif // TOOLTIPRESULTWIDGET_H diff --git a/src/globalsearch/urlsearchprovider.cpp b/src/globalsearch/urlsearchprovider.cpp index c22979a23..077005b16 100644 --- a/src/globalsearch/urlsearchprovider.cpp +++ b/src/globalsearch/urlsearchprovider.cpp @@ -36,8 +36,7 @@ UrlSearchProvider::UrlSearchProvider(Application* app, QObject* parent) void UrlSearchProvider::SearchAsync(int id, const QString& query) { Result result(this); - result.match_quality_ = globalsearch::Quality_AtStart; - result.type_ = globalsearch::Type_Stream; + result.group_automatically_ = false; result.metadata_.set_url(QUrl::fromUserInput(query)); result.metadata_.set_filetype(Song::Type_Stream); diff --git a/src/library/librarymodel.cpp b/src/library/librarymodel.cpp index 08717c61f..2d496091b 100644 --- a/src/library/librarymodel.cpp +++ b/src/library/librarymodel.cpp @@ -949,20 +949,20 @@ void LibraryModel::FinishItem(GroupBy type, } } -QString LibraryModel::TextOrUnknown(const QString& text) const { +QString LibraryModel::TextOrUnknown(const QString& text) { if (text.isEmpty()) { return tr("Unknown"); } return text; } -QString LibraryModel::PrettyYearAlbum(int year, const QString& album) const { +QString LibraryModel::PrettyYearAlbum(int year, const QString& album) { if (year <= 0) return TextOrUnknown(album); return QString::number(year) + " - " + TextOrUnknown(album); } -QString LibraryModel::SortText(QString text) const { +QString LibraryModel::SortText(QString text) { if (text.isEmpty()) { text = " unknown"; } else { @@ -973,7 +973,7 @@ QString LibraryModel::SortText(QString text) const { return text; } -QString LibraryModel::SortTextForArtist(QString artist) const { +QString LibraryModel::SortTextForArtist(QString artist) { artist = SortText(artist); if (artist.startsWith("the ")) { @@ -983,12 +983,12 @@ QString LibraryModel::SortTextForArtist(QString artist) const { return artist; } -QString LibraryModel::SortTextForYear(int year) const { +QString LibraryModel::SortTextForYear(int year) { QString str = QString::number(year); return QString("0").repeated(qMax(0, 4 - str.length())) + str; } -QString LibraryModel::SortTextForSong(const Song& song) const { +QString LibraryModel::SortTextForSong(const Song& song) { QString ret = QString::number(qMax(0, song.disc()) * 1000 + qMax(0, song.track())); ret.prepend(QString("0").repeated(6 - ret.length())); ret.append(song.url().toString()); diff --git a/src/library/librarymodel.h b/src/library/librarymodel.h index eadd82f03..8fa94f01f 100644 --- a/src/library/librarymodel.h +++ b/src/library/librarymodel.h @@ -140,6 +140,14 @@ class LibraryModel : public SimpleTreeModel { //Whether or not to show letters heading in the library view void set_show_dividers(bool show_dividers); + // Utility functions for manipulating text + static QString TextOrUnknown(const QString& text); + static QString PrettyYearAlbum(int year, const QString& album); + static QString SortText(QString text); + static QString SortTextForArtist(QString artist); + static QString SortTextForYear(int year); + static QString SortTextForSong(const Song& song); + signals: void TotalSongCountUpdated(int count); void GroupingChanged(const LibraryModel::Grouping& g); @@ -210,15 +218,6 @@ class LibraryModel : public SimpleTreeModel { void FinishItem(GroupBy type, bool signal, bool create_divider, LibraryItem* parent, LibraryItem* item); - // Functions for manipulating text - QString TextOrUnknown(const QString& text) const; - QString PrettyYearAlbum(int year, const QString& album) const; - - QString SortText(QString text) const; - QString SortTextForArtist(QString artist) const; - QString SortTextForYear(int year) const; - QString SortTextForSong(const Song& song) const; - QString DividerKey(GroupBy type, LibraryItem* item) const; QString DividerDisplayText(GroupBy type, const QString& key) const; diff --git a/src/library/libraryview.cpp b/src/library/libraryview.cpp index 67923eb37..9bb1304d7 100644 --- a/src/library/libraryview.cpp +++ b/src/library/libraryview.cpp @@ -62,13 +62,41 @@ void LibraryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o painter->save(); + QRect text_rect(opt.rect); + + // Does this item have an icon? + QPixmap pixmap; + QVariant decoration = index.data(Qt::DecorationRole); + if (!decoration.isNull()) { + if (decoration.canConvert()) { + pixmap = decoration.value(); + } else if (decoration.canConvert()) { + pixmap = decoration.value().pixmap(opt.decorationSize); + } + } + + // Draw the icon at the left of the text rectangle + if (!pixmap.isNull()) { + text_rect.setLeft(text_rect.left() + 15); + + QRect icon_rect(text_rect.topLeft(), opt.decorationSize); + const int padding = (text_rect.height() - icon_rect.height()) / 2; + icon_rect.adjust(padding, padding, padding, padding); + text_rect.moveLeft(icon_rect.right() + padding + 6); + + if (pixmap.size() != opt.decorationSize) { + pixmap = pixmap.scaled(opt.decorationSize, Qt::KeepAspectRatio); + } + + painter->drawPixmap(icon_rect, pixmap); + } else { + text_rect.setLeft(text_rect.left() + 30); + } + // Draw the text QFont bold_font(opt.font); bold_font.setBold(true); - QRect text_rect(opt.rect); - text_rect.setLeft(text_rect.left() + 30); - painter->setPen(opt.palette.color(QPalette::Text)); painter->setFont(bold_font); painter->drawText(text_rect, text); diff --git a/src/main.cpp b/src/main.cpp index ec0b0f865..0022c9f16 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -99,7 +99,6 @@ using boost::scoped_ptr; #include "core/mpris.h" #include "core/mpris2.h" #include "dbus/metatypes.h" - #include "globalsearch/globalsearchservice.h" #include #include #include @@ -418,8 +417,6 @@ int main(int argc, char *argv[]) { qDBusRegisterMetaType >(); mpris::Mpris mpris(&app); - - GlobalSearchService global_search_service(app.global_search()); #endif // Window diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index 9caf3c3ec..354dba59d 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -42,6 +42,7 @@ #include "engines/enginebase.h" #include "engines/gstengine.h" #include "globalsearch/globalsearch.h" +#include "globalsearch/globalsearchview.h" #include "globalsearch/librarysearchprovider.h" #include "internet/jamendoservice.h" #include "internet/magnatuneservice.h" @@ -162,6 +163,7 @@ MainWindow::MainWindow(Application* app, osd_(osd), global_shortcuts_(new GlobalShortcuts(this)), remote_(NULL), + global_search_view_(new GlobalSearchView(app_, this)), library_view_(new LibraryViewContainer(this)), file_view_(new FileView(this)), internet_view_(new InternetViewContainer(this)), @@ -218,11 +220,11 @@ MainWindow::MainWindow(Application* app, app_->global_search()->ReloadSettings(); - ui_->global_search->Init(app_->global_search()); - connect(ui_->global_search, SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); - connect(ui_->global_search, SIGNAL(OpenSettingsAtPage(SettingsDialog::Page)), SLOT(OpenSettingsDialogAtPage(SettingsDialog::Page))); + connect(global_search_view_, SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); + connect(global_search_view_, SIGNAL(OpenSettingsAtPage(SettingsDialog::Page)), SLOT(OpenSettingsDialogAtPage(SettingsDialog::Page))); // Add tabs to the fancy tab widget + ui_->tabs->AddTab(global_search_view_, IconLoader::Load("search"), tr("Search")); ui_->tabs->AddTab(library_view_, IconLoader::Load("folder-sound"), tr("Library")); ui_->tabs->AddTab(file_view_, IconLoader::Load("document-open"), tr("Files")); ui_->tabs->AddTab(internet_view_, IconLoader::Load("applications-internet"), tr("Internet")); @@ -759,7 +761,6 @@ void MainWindow::ReloadAllSettings() { // Other settings app_->ReloadSettings(); app_->global_search()->ReloadSettings(); - ui_->global_search->ReloadSettings(); app_->library()->ReloadSettings(); app_->player()->ReloadSettings(); osd_->ReloadSettings(); @@ -2023,7 +2024,7 @@ void MainWindow::ConnectInfoView(SongInfoBase* view) { connect(view, SIGNAL(ShowSettingsDialog()), SLOT(ShowSongInfoConfig())); connect(view, SIGNAL(DoGlobalSearch(QString)), - ui_->global_search, SLOT(StartSearch(QString))); + global_search_view_, SLOT(StartSearch(QString))); } void MainWindow::AddSongInfoGenerator(smart_playlists::GeneratorPtr gen) { diff --git a/src/ui/mainwindow.h b/src/ui/mainwindow.h index 3cd26227e..67991d80b 100644 --- a/src/ui/mainwindow.h +++ b/src/ui/mainwindow.h @@ -49,6 +49,7 @@ class Equalizer; class ErrorDialog; class FileView; class GlobalSearch; +class GlobalSearchView; class GlobalShortcuts; class GroupByDialog; class Library; @@ -272,6 +273,7 @@ class MainWindow : public QMainWindow, public PlatformInterface { GlobalShortcuts* global_shortcuts_; Remote* remote_; + GlobalSearchView* global_search_view_; LibraryViewContainer* library_view_; FileView* file_view_; InternetViewContainer* internet_view_; diff --git a/src/ui/mainwindow.ui b/src/ui/mainwindow.ui index bd7bb03a1..754667f99 100644 --- a/src/ui/mainwindow.ui +++ b/src/ui/mainwindow.ui @@ -35,9 +35,6 @@ 0 - - - @@ -418,7 +415,7 @@ 0 0 1131 - 23 + 24 @@ -900,12 +897,6 @@ QWidget
widgets/fancytabwidget.h
- - GlobalSearchWidget - QWidget -
globalsearch/globalsearchwidget.h
- 1 -