diff options
Diffstat (limited to 'src/search-tool-qt/search-tool-qt.cc')
-rw-r--r-- | src/search-tool-qt/search-tool-qt.cc | 189 |
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> "; +#else + string += "<br><small> "; +#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) |