summaryrefslogtreecommitdiff
path: root/src/search-tool-qt/search-tool-qt.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/search-tool-qt/search-tool-qt.cc')
-rw-r--r--src/search-tool-qt/search-tool-qt.cc189
1 files changed, 164 insertions, 25 deletions
diff --git a/src/search-tool-qt/search-tool-qt.cc b/src/search-tool-qt/search-tool-qt.cc
index f7b478b..9818c69 100644
--- a/src/search-tool-qt/search-tool-qt.cc
+++ b/src/search-tool-qt/search-tool-qt.cc
@@ -1,6 +1,6 @@
/*
* search-tool-qt.cc
- * Copyright 2011-2015 John Lindgren
+ * Copyright 2011-2017 John Lindgren and René J.V. Bertin
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -21,6 +21,8 @@
#include <glib.h>
#include <QAbstractListModel>
+#include <QAbstractTextDocumentLayout>
+#include <QApplication>
#include <QBoxLayout>
#include <QContextMenuEvent>
#include <QIcon>
@@ -28,7 +30,9 @@
#include <QLineEdit>
#include <QMenu>
#include <QMimeData>
+#include <QPainter>
#include <QPushButton>
+#include <QStyledItemDelegate>
#include <QTreeView>
#include <QUrl>
@@ -37,34 +41,58 @@
#include <libaudcore/i18n.h>
#include <libaudcore/playlist.h>
#include <libaudcore/plugin.h>
+#include <libaudcore/preferences.h>
#include <libaudcore/mainloop.h>
#include <libaudcore/multihash.h>
#include <libaudcore/runtime.h>
#include <libaudqt/libaudqt.h>
#include <libaudqt/menu.h>
-#define MAX_RESULTS 20
+#define CFG_ID "search-tool"
#define SEARCH_DELAY 300
class SearchToolQt : public GeneralPlugin
{
public:
+ static const char * const defaults[];
+ static const PreferencesWidget widgets[];
+ static const PluginPreferences prefs;
+
static constexpr PluginInfo info = {
N_("Search Tool"),
PACKAGE,
nullptr, // about
- nullptr, // prefs
+ & prefs,
PluginQtOnly
};
constexpr SearchToolQt () : GeneralPlugin (info, false) {}
+ bool init ();
void * get_qt_widget ();
int take_message (const char * code, const void *, int);
};
EXPORT SearchToolQt aud_plugin_instance;
+static void trigger_search ();
+
+const char * const SearchToolQt::defaults[] = {
+ "max_results", "20",
+ "rescan_on_startup", "FALSE",
+ nullptr
+};
+
+const PreferencesWidget SearchToolQt::widgets[] = {
+ WidgetSpin (N_("Number of results to show:"),
+ WidgetInt (CFG_ID, "max_results", trigger_search),
+ {10, 10000, 10}),
+ WidgetCheck (N_("Rescan library at startup"),
+ WidgetBool (CFG_ID, "rescan_on_startup"))
+};
+
+const PluginPreferences SearchToolQt::prefs = {{widgets}};
+
enum class SearchField {
Genre,
Artist,
@@ -131,13 +159,82 @@ private:
int m_rows = 0;
};
+// Allow rich text in QTreeView entries
+// https://stackoverflow.com/questions/1956542/how-to-make-item-view-render-rich-html-text-in-qt
+class HtmlDelegate : public QStyledItemDelegate
+{
+protected:
+ void paint (QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const;
+ QSize sizeHint (const QStyleOptionViewItem & option, const QModelIndex & index) const;
+};
+
+static void init_text_document (QTextDocument & doc, const QStyleOptionViewItem & option)
+{
+ doc.setHtml (option.text);
+ doc.setDocumentMargin (audqt::sizes.TwoPt);
+ doc.setDefaultFont (option.font);
+}
+
+void HtmlDelegate::paint (QPainter * painter, const QStyleOptionViewItem & option_, const QModelIndex & index) const
+{
+ QStyleOptionViewItem option = option_;
+ initStyleOption (& option, index);
+
+ QStyle * style = option.widget ? option.widget->style () : qApp->style ();
+
+ QTextDocument doc;
+ init_text_document (doc, option);
+
+ /// Painting item without text
+ option.text = QString ();
+ style->drawControl (QStyle::CE_ItemViewItem, & option, painter);
+
+ QAbstractTextDocumentLayout::PaintContext ctx;
+
+ // Color group logic imitating qcommonstyle.cpp
+ QPalette::ColorGroup cg =
+ ! (option.state & QStyle::State_Enabled) ? QPalette::Disabled :
+ ! (option.state & QStyle::State_Active) ? QPalette::Inactive : QPalette::Normal;
+
+ // Highlighting text if item is selected
+ if (option.state & QStyle::State_Selected)
+ ctx.palette.setColor (QPalette::Text, option.palette.color (cg, QPalette::HighlightedText));
+ else
+ ctx.palette.setColor (QPalette::Text, option.palette.color (cg, QPalette::Text));
+
+ QRect textRect = style->subElementRect (QStyle::SE_ItemViewItemText, & option);
+ painter->save ();
+ painter->translate (textRect.topLeft ());
+ painter->setClipRect (textRect.translated (-textRect.topLeft ()));
+ doc.documentLayout ()->draw (painter, ctx);
+ painter->restore ();
+}
+
+QSize HtmlDelegate::sizeHint (const QStyleOptionViewItem & option_, const QModelIndex & index) const
+{
+ QStyleOptionViewItem option = option_;
+ initStyleOption (& option, index);
+
+ QTextDocument doc;
+ init_text_document (doc, option);
+
+ return QSize (audqt::sizes.OneInch, doc.size ().height ());
+}
+
class ResultsView : public QTreeView
{
+public:
+ ResultsView ()
+ { setItemDelegate (& m_delegate); }
+
protected:
void contextMenuEvent (QContextMenuEvent * event);
+
+private:
+ HtmlDelegate m_delegate;
};
-static StringBuf create_item_label (int row);
+static QString create_item_label (int row);
static Playlist s_playlist;
static Index<String> s_search_terms;
@@ -193,7 +290,7 @@ void ResultsModel::update ()
QVariant ResultsModel::data (const QModelIndex & index, int role) const
{
if (role == Qt::DisplayRole)
- return QString ((const char *) create_item_label (index.row ()));
+ return create_item_label (index.row ());
else
return QVariant ();
}
@@ -371,11 +468,12 @@ static void do_search ()
/* first sort by number of songs per item */
s_items.sort (item_compare_pass1);
+ int max_results = aud_get_int (CFG_ID, "max_results");
/* limit to items with most songs */
- if (s_items.len () > MAX_RESULTS)
+ if (s_items.len () > max_results)
{
- s_hidden_items = s_items.len () - MAX_RESULTS;
- s_items.remove (MAX_RESULTS, -1);
+ s_hidden_items = s_items.len () - max_results;
+ s_items.remove (max_results, -1);
}
/* sort by item type, then item name */
@@ -495,6 +593,12 @@ static void search_timeout (void * = nullptr)
s_search_pending = false;
}
+static void trigger_search ()
+{
+ s_search_timer.queue (SEARCH_DELAY, search_timeout, nullptr);
+ s_search_pending = true;
+}
+
static void update_database ()
{
if (check_playlist (true, true))
@@ -570,6 +674,9 @@ static void search_init ()
{
find_playlist ();
+ if (aud_get_bool (CFG_ID, "rescan_on_startup"))
+ begin_add (get_uri ());
+
update_database ();
hook_associate ("playlist add complete", add_complete_cb, nullptr);
@@ -662,37 +769,59 @@ static void action_add_to_playlist ()
do_add (false, false);
}
-static StringBuf create_item_label (int row)
+static QString create_item_label (int row)
{
+ static constexpr aud::array<SearchField, const char *> start_tags =
+ {"", "<b>", "<i>", ""};
+ static constexpr aud::array<SearchField, const char *> end_tags =
+ {"", "</b>", "</i>", ""};
+
if (row < 0 || row >= s_items.len ())
- return StringBuf ();
+ return QString ();
const Item * item = s_items[row];
- StringBuf string = str_concat ({item->name, "\n"});
+
+ QString string = start_tags[item->field];
+
+ string += QString ((item->field == SearchField::Genre) ?
+ str_toupper_utf8 (item->name) : item->name).toHtmlEscaped ();
+
+ string += end_tags[item->field];
+
+#ifdef Q_OS_MAC // Mac-specific font tweaks
+ string += "<br>&nbsp;";
+#else
+ string += "<br><small>&nbsp;";
+#endif
if (item->field != SearchField::Title)
{
- string.insert (-1, " ");
- string.combine (str_printf (dngettext (PACKAGE, "%d song", "%d songs",
- item->matches.len ()), item->matches.len ()));
+ string += str_printf (dngettext (PACKAGE, "%d song", "%d songs",
+ item->matches.len ()), item->matches.len ());
+
+ if (item->field == SearchField::Genre || item->parent)
+ string += ' ';
}
if (item->field == SearchField::Genre)
{
- string.insert (-1, " ");
- string.insert (-1, _("of this genre"));
+ string += _("of this genre");
}
-
- if (item->parent)
+ else if (item->parent)
{
auto parent = (item->parent->parent ? item->parent->parent : item->parent);
- string.insert (-1, " ");
- string.insert (-1, (parent->field == SearchField::Album) ? _("on") : _("by"));
- string.insert (-1, " ");
- string.insert (-1, parent->name);
+ string += (parent->field == SearchField::Album) ? _("on") : _("by");
+ string += ' ';
+ string += start_tags[parent->field];
+ string += QString (parent->name).toHtmlEscaped ();
+ string += end_tags[parent->field];
}
+#ifndef Q_OS_MAC // Mac-specific font tweaks
+ string += "</small>";
+#endif
+
return string;
}
@@ -738,6 +867,12 @@ QMimeData * ResultsModel::mimeData (const QModelIndexList & indexes) const
return data;
}
+bool SearchToolQt::init ()
+{
+ aud_config_set_defaults (CFG_ID, defaults);
+ return true;
+}
+
void * SearchToolQt::get_qt_widget ()
{
s_search_entry = new QLineEdit;
@@ -767,9 +902,14 @@ void * SearchToolQt::get_qt_widget ()
s_stats_label->setAlignment (Qt::AlignCenter);
s_stats_label->setContentsMargins (audqt::margins.TwoPt);
+#ifdef Q_OS_MAC // Mac-specific font tweaks
+ s_search_entry->setFont (QApplication::font ("QTreeView"));
+ s_stats_label->setFont (QApplication::font ("QSmallFont"));
+#endif
+
auto chooser = new QLineEdit;
- auto button = new QPushButton (QIcon::fromTheme ("view-refresh"), QString ());
+ auto button = new QPushButton (audqt::get_icon ("view-refresh"), QString ());
button->setFlat (true);
button->setFocusPolicy (Qt::NoFocus);
@@ -802,8 +942,7 @@ void * SearchToolQt::get_qt_widget ()
QObject::connect (s_search_entry, & QLineEdit::textEdited, [] (const QString & text)
{
s_search_terms = str_list_to_index (str_tolower_utf8 (text.toUtf8 ()), " ");
- s_search_timer.queue (SEARCH_DELAY, search_timeout, nullptr);
- s_search_pending = true;
+ trigger_search ();
});
QObject::connect (chooser, & QLineEdit::textEdited, [button] (const QString & text)