Resurrect filtering of lists (playlist only for now)
This commit is contained in:
1
NEWS
1
NEWS
@@ -1,6 +1,7 @@
|
||||
ncmpcpp-0.8 (????-??-??)
|
||||
* Configuration variable 'execute_on_player_state_change' was added.
|
||||
* Support for controlling whether ncmpcpp should display multiple tags as-is or make an effort to hide duplicate values (show_duplicate_tags configuration variable, enabled by default).
|
||||
* Support for filtering of lists was brought back from the dead.
|
||||
|
||||
ncmpcpp-0.7.7 (2016-10-31)
|
||||
* Fixed compilation on 32bit platforms.
|
||||
|
||||
@@ -880,7 +880,12 @@ void MoveSelectedItemsUp::run()
|
||||
{
|
||||
if (myScreen == myPlaylist)
|
||||
{
|
||||
moveSelectedItemsUp(myPlaylist->main(), std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
|
||||
if (myPlaylist->main().isFiltered())
|
||||
Statusbar::print("Moving items up is disabled in filtered playlist");
|
||||
else
|
||||
moveSelectedItemsUp(
|
||||
myPlaylist->main(),
|
||||
std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
|
||||
}
|
||||
else if (myScreen == myPlaylistEditor)
|
||||
{
|
||||
@@ -903,7 +908,12 @@ void MoveSelectedItemsDown::run()
|
||||
{
|
||||
if (myScreen == myPlaylist)
|
||||
{
|
||||
moveSelectedItemsDown(myPlaylist->main(), std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
|
||||
if (myPlaylist->main().isFiltered())
|
||||
Statusbar::print("Moving items down is disabled in filtered playlist");
|
||||
else
|
||||
moveSelectedItemsDown(
|
||||
myPlaylist->main(),
|
||||
std::bind(&MPD::Connection::Move, ph::_1, ph::_2, ph::_3));
|
||||
}
|
||||
else if (myScreen == myPlaylistEditor)
|
||||
{
|
||||
@@ -1157,7 +1167,7 @@ void TogglePlayingSongCentering::run()
|
||||
{
|
||||
auto s = myPlaylist->nowPlayingSong();
|
||||
if (!s.empty())
|
||||
myPlaylist->main().highlight(s.getPosition());
|
||||
myPlaylist->moveToSong(s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1187,7 +1197,7 @@ void JumpToPlayingSong::run()
|
||||
return;
|
||||
if (myScreen == myPlaylist)
|
||||
{
|
||||
myPlaylist->main().highlight(s.getPosition());
|
||||
myPlaylist->moveToSong(s);
|
||||
}
|
||||
else if (myScreen == myBrowser)
|
||||
{
|
||||
@@ -1936,11 +1946,47 @@ void ReversePlaylist::run()
|
||||
|
||||
bool ApplyFilter::canBeRun()
|
||||
{
|
||||
return false;
|
||||
m_searchable = dynamic_cast<Searchable *>(myScreen);
|
||||
return m_searchable != nullptr
|
||||
&& myScreen == myPlaylist;
|
||||
}
|
||||
|
||||
void ApplyFilter::run()
|
||||
{ }
|
||||
{
|
||||
using Global::wFooter;
|
||||
|
||||
std::string filter = m_searchable->currentFilter();
|
||||
if (!filter.empty())
|
||||
{
|
||||
m_searchable->applyFilter(filter);
|
||||
myScreen->refreshWindow();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Statusbar::ScopedLock slock;
|
||||
NC::Window::ScopedPromptHook helper(
|
||||
*wFooter,
|
||||
Statusbar::Helpers::ApplyFilterImmediately(m_searchable));
|
||||
Statusbar::put() << "Apply filter: ";
|
||||
filter = wFooter->prompt(filter);
|
||||
}
|
||||
catch (NC::PromptAborted &)
|
||||
{
|
||||
m_searchable->applyFilter(filter);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (filter.empty())
|
||||
Statusbar::printf("Filtering disabled");
|
||||
else
|
||||
Statusbar::printf("Using filter \"%1%\"", filter);
|
||||
|
||||
if (myScreen == myPlaylist)
|
||||
myPlaylist->reloadTotalLength();
|
||||
|
||||
listsChangeFinisher();
|
||||
}
|
||||
|
||||
bool Find::canBeRun()
|
||||
{
|
||||
@@ -2952,33 +2998,30 @@ void findItem(const SearchDirection direction)
|
||||
assert(w != nullptr);
|
||||
assert(w->allowsSearching());
|
||||
|
||||
std::string constraint;
|
||||
{
|
||||
Statusbar::ScopedLock slock;
|
||||
NC::Window::ScopedPromptHook prompt_hook(*wFooter,
|
||||
Statusbar::Helpers::FindImmediately(w, direction)
|
||||
);
|
||||
Statusbar::put() << (boost::format("Find %1%: ") % direction).str();
|
||||
constraint = wFooter->prompt(w->searchConstraint());
|
||||
}
|
||||
|
||||
std::string constraint = w->searchConstraint();
|
||||
try
|
||||
{
|
||||
if (constraint.empty())
|
||||
{
|
||||
Statusbar::printf("Constraint unset");
|
||||
w->clearSearchConstraint();
|
||||
}
|
||||
else
|
||||
{
|
||||
w->setSearchConstraint(constraint);
|
||||
Statusbar::printf("Using constraint \"%1%\"", constraint);
|
||||
}
|
||||
Statusbar::ScopedLock slock;
|
||||
NC::Window::ScopedPromptHook prompt_hook(
|
||||
*wFooter,
|
||||
Statusbar::Helpers::FindImmediately(w, direction));
|
||||
Statusbar::put() << (boost::format("Find %1%: ") % direction).str();
|
||||
constraint = wFooter->prompt(constraint);
|
||||
}
|
||||
catch (boost::bad_expression &e)
|
||||
catch (NC::PromptAborted &)
|
||||
{
|
||||
Statusbar::printf("%1%", e.what());
|
||||
w->setSearchConstraint(constraint);
|
||||
w->search(direction, Config.wrapped_search, false);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (constraint.empty())
|
||||
{
|
||||
Statusbar::printf("Constraint unset");
|
||||
w->clearSearchConstraint();
|
||||
}
|
||||
else
|
||||
Statusbar::printf("Using constraint \"%1%\"", constraint);
|
||||
}
|
||||
|
||||
void listsChangeFinisher()
|
||||
|
||||
@@ -1044,6 +1044,8 @@ struct ApplyFilter: public BaseAction
|
||||
private:
|
||||
virtual bool canBeRun() OVERRIDE;
|
||||
virtual void run() OVERRIDE;
|
||||
|
||||
Searchable *m_searchable;
|
||||
};
|
||||
|
||||
struct Find: BaseAction
|
||||
|
||||
@@ -101,9 +101,10 @@ void setProperties(NC::Menu<T> &menu, const MPD::Song &s, const SongList &list,
|
||||
is_selected = menu.drawn()->isSelected();
|
||||
discard_colors = Config.discard_colors_if_item_is_selected && is_selected;
|
||||
|
||||
int song_pos = drawn_pos;
|
||||
is_now_playing = Status::State::player() != MPD::psStop && myPlaylist->isActiveWindow(menu)
|
||||
&& song_pos == Status::State::currentSongPosition();
|
||||
int song_pos = s.getPosition();
|
||||
is_now_playing = Status::State::player() != MPD::psStop
|
||||
&& myPlaylist->isActiveWindow(menu)
|
||||
&& song_pos == Status::State::currentSongPosition();
|
||||
if (is_now_playing)
|
||||
menu << Config.now_playing_prefix;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,40 @@
|
||||
#include "utility/type_conversions.h"
|
||||
#include "utility/wide_string.h"
|
||||
|
||||
enum ReapplyFilter { Yes, No };
|
||||
|
||||
template <typename ItemT, ReapplyFilter reapplyFilter>
|
||||
struct ScopedUnfilteredMenu
|
||||
{
|
||||
ScopedUnfilteredMenu(NC::Menu<ItemT> &menu)
|
||||
: m_menu(menu)
|
||||
{
|
||||
m_is_filtered = m_menu.isFiltered();
|
||||
if (m_is_filtered)
|
||||
m_menu.showAllItems();
|
||||
}
|
||||
|
||||
~ScopedUnfilteredMenu()
|
||||
{
|
||||
if (m_is_filtered)
|
||||
{
|
||||
switch (reapplyFilter)
|
||||
{
|
||||
case ReapplyFilter::Yes:
|
||||
m_menu.reapplyFilter();
|
||||
break;
|
||||
case ReapplyFilter::No:
|
||||
m_menu.showFilteredItems();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_is_filtered;
|
||||
NC::Menu<ItemT> &m_menu;
|
||||
};
|
||||
|
||||
template <typename Iterator, typename PredicateT>
|
||||
Iterator wrappedSearch(Iterator begin, Iterator current, Iterator end,
|
||||
const PredicateT &pred, bool wrap, bool skip_current)
|
||||
|
||||
@@ -37,6 +37,9 @@ struct Searchable
|
||||
virtual void setSearchConstraint(const std::string &constraint) = 0;
|
||||
virtual void clearSearchConstraint() = 0;
|
||||
virtual bool search(SearchDirection direction, bool wrap, bool skip_current) = 0;
|
||||
|
||||
virtual std::string currentFilter() { return ""; }
|
||||
virtual void applyFilter(const std::string &) { }
|
||||
};
|
||||
|
||||
struct HasActions
|
||||
|
||||
159
src/menu.h
159
src/menu.h
@@ -21,7 +21,6 @@
|
||||
#ifndef NCMPCPP_MENU_H
|
||||
#define NCMPCPP_MENU_H
|
||||
|
||||
#include <boost/iterator/indirect_iterator.hpp>
|
||||
#include <boost/iterator/transform_iterator.hpp>
|
||||
#include <boost/range/detail/any_iterator.hpp>
|
||||
#include <cassert>
|
||||
@@ -96,9 +95,6 @@ struct List
|
||||
bool isInactive() const { return m_properties & Inactive; }
|
||||
bool isSeparator() const { return m_properties & Separator; }
|
||||
|
||||
protected:
|
||||
unsigned properties() const { return m_properties; }
|
||||
|
||||
private:
|
||||
unsigned m_properties;
|
||||
};
|
||||
@@ -154,51 +150,93 @@ inline List::ConstIterator begin(const List &list) { return list.beginP(); }
|
||||
inline List::Iterator end(List &list) { return list.endP(); }
|
||||
inline List::ConstIterator end(const List &list) { return list.endP(); }
|
||||
|
||||
/// This template class is generic menu capable of
|
||||
/// holding any std::vector compatible values.
|
||||
template <typename ItemT> struct Menu : Window, List
|
||||
/// Generic menu capable of holding any std::vector compatible values.
|
||||
template <typename ItemT>
|
||||
struct Menu: Window, List
|
||||
{
|
||||
struct Item : List::Properties
|
||||
struct Item
|
||||
{
|
||||
template <bool Const>
|
||||
struct PropertiesExtractor
|
||||
{
|
||||
typedef PropertiesExtractor type;
|
||||
|
||||
typedef typename std::conditional<Const, const Properties, Properties>::type Properties_;
|
||||
typedef typename std::conditional<Const, const Item, Item>::type Item_;
|
||||
|
||||
Properties_ &operator()(Item_ &i) const {
|
||||
return static_cast<Properties_ &>(i);
|
||||
}
|
||||
};
|
||||
friend struct Menu<ItemT>;
|
||||
|
||||
typedef ItemT Type;
|
||||
|
||||
friend struct Menu<ItemT>;
|
||||
|
||||
|
||||
Item()
|
||||
: m_value(std::make_shared<ItemT>(ItemT()))
|
||||
: m_impl(std::make_shared<std::tuple<ItemT, Properties>>())
|
||||
{ }
|
||||
|
||||
template <typename ValueT>
|
||||
Item(ValueT &&value_, Properties::Type properties)
|
||||
: Properties(properties)
|
||||
, m_value(std::make_shared<ItemT>(std::forward<ValueT>(value_)))
|
||||
template <typename ValueT, typename PropertiesT>
|
||||
Item(ValueT &&value_, PropertiesT properties_)
|
||||
: m_impl(
|
||||
std::make_shared<std::tuple<ItemT, List::Properties>>(
|
||||
std::forward<ValueT>(value_),
|
||||
std::forward<PropertiesT>(properties_)))
|
||||
{ }
|
||||
|
||||
ItemT &value() { return *m_value; }
|
||||
const ItemT &value() const { return *m_value; }
|
||||
|
||||
ItemT &operator*() { return *m_value; }
|
||||
const ItemT &operator*() const { return *m_value; }
|
||||
|
||||
ItemT &value() { return std::get<0>(*m_impl); }
|
||||
const ItemT &value() const { return std::get<0>(*m_impl); }
|
||||
|
||||
Properties &properties() { return std::get<1>(*m_impl); }
|
||||
const Properties &properties() const { return std::get<1>(*m_impl); }
|
||||
|
||||
// Forward methods to List::Properties.
|
||||
void setBold (bool is_bold) { properties().setBold(is_bold); }
|
||||
void setSelectable(bool is_selectable) { properties().setSelectable(is_selectable); }
|
||||
void setSelected (bool is_selected) { properties().setSelected(is_selected); }
|
||||
void setInactive (bool is_inactive) { properties().setInactive(is_inactive); }
|
||||
void setSeparator (bool is_separator) { properties().setSeparator(is_separator); }
|
||||
|
||||
bool isBold() const { return properties().isBold(); }
|
||||
bool isSelectable() const { return properties().isSelectable(); }
|
||||
bool isSelected() const { return properties().isSelected(); }
|
||||
bool isInactive() const { return properties().isInactive(); }
|
||||
bool isSeparator() const { return properties().isSeparator(); }
|
||||
|
||||
// Make a deep copy of Item.
|
||||
Item copy() const {
|
||||
return Item(*m_value, static_cast<Properties::Type>(properties()));
|
||||
return Item(value(), properties());
|
||||
}
|
||||
|
||||
private:
|
||||
enum class Const { Yes, No };
|
||||
|
||||
template <Const const_>
|
||||
struct ExtractProperties
|
||||
{
|
||||
typedef ExtractProperties type;
|
||||
|
||||
typedef typename std::conditional<
|
||||
const_ == Const::Yes,
|
||||
const Properties,
|
||||
Properties>::type Properties_;
|
||||
typedef typename std::conditional<
|
||||
const_ == Const::Yes,
|
||||
const Item,
|
||||
Item>::type Item_;
|
||||
|
||||
Properties_ &operator()(Item_ &i) const {
|
||||
return i.properties();
|
||||
}
|
||||
};
|
||||
|
||||
template <Const const_>
|
||||
struct ExtractValue
|
||||
{
|
||||
typedef ExtractValue type;
|
||||
|
||||
typedef typename std::conditional<
|
||||
const_ == Const::Yes,
|
||||
const ItemT,
|
||||
ItemT>::type Value_;
|
||||
typedef typename std::conditional<
|
||||
const_ == Const::Yes,
|
||||
const Item,
|
||||
Item>::type Item_;
|
||||
|
||||
Value_ &operator()(Item_ &i) const {
|
||||
return i.value();
|
||||
}
|
||||
};
|
||||
|
||||
static Item mkSeparator()
|
||||
{
|
||||
Item item;
|
||||
@@ -207,7 +245,7 @@ template <typename ItemT> struct Menu : Window, List
|
||||
return item;
|
||||
}
|
||||
|
||||
std::shared_ptr<ItemT> m_value;
|
||||
std::shared_ptr<std::tuple<ItemT, Properties>> m_impl;
|
||||
};
|
||||
|
||||
typedef typename std::vector<Item>::iterator Iterator;
|
||||
@@ -215,33 +253,29 @@ template <typename ItemT> struct Menu : Window, List
|
||||
typedef std::reverse_iterator<Iterator> ReverseIterator;
|
||||
typedef std::reverse_iterator<ConstIterator> ConstReverseIterator;
|
||||
|
||||
typedef boost::indirect_iterator<
|
||||
Iterator,
|
||||
ItemT,
|
||||
boost::random_access_traversal_tag
|
||||
> ValueIterator;
|
||||
typedef boost::indirect_iterator<
|
||||
ConstIterator,
|
||||
const ItemT,
|
||||
boost::random_access_traversal_tag
|
||||
> ConstValueIterator;
|
||||
typedef boost::transform_iterator<
|
||||
typename Item::template ExtractValue<Item::Const::No>,
|
||||
Iterator> ValueIterator;
|
||||
typedef boost::transform_iterator<
|
||||
typename Item::template ExtractValue<Item::Const::Yes>,
|
||||
ConstIterator> ConstValueIterator;
|
||||
typedef std::reverse_iterator<ValueIterator> ReverseValueIterator;
|
||||
typedef std::reverse_iterator<ConstValueIterator> ConstReverseValueIterator;
|
||||
|
||||
typedef boost::transform_iterator<
|
||||
typename Item::template PropertiesExtractor<false>,
|
||||
Iterator
|
||||
> PropertiesIterator;
|
||||
typename Item::template ExtractProperties<Item::Const::No>,
|
||||
Iterator> PropertiesIterator;
|
||||
typedef boost::transform_iterator<
|
||||
typename Item::template PropertiesExtractor<true>,
|
||||
ConstIterator
|
||||
> ConstPropertiesIterator;
|
||||
typename Item::template ExtractProperties<Item::Const::Yes>,
|
||||
ConstIterator> ConstPropertiesIterator;
|
||||
|
||||
/// Function helper prototype used to display each option on the screen.
|
||||
/// If not set by setItemDisplayer(), menu won't display anything.
|
||||
/// @see setItemDisplayer()
|
||||
typedef std::function<void(Menu<ItemT> &)> ItemDisplayer;
|
||||
|
||||
|
||||
typedef std::function<bool(const Item &)> FilterPredicate;
|
||||
|
||||
Menu();
|
||||
|
||||
Menu(size_t startx, size_t starty, size_t width, size_t height,
|
||||
@@ -253,7 +287,8 @@ template <typename ItemT> struct Menu : Window, List
|
||||
|
||||
/// Sets helper function that is responsible for displaying items
|
||||
/// @param ptr function pointer that matches the ItemDisplayer prototype
|
||||
void setItemDisplayer(const ItemDisplayer &f) { m_item_displayer = f; }
|
||||
template <typename ItemDisplayerT>
|
||||
void setItemDisplayer(ItemDisplayerT &&displayer);
|
||||
|
||||
/// Resizes the list to given size (adequate to std::vector::resize())
|
||||
/// @param size requested size
|
||||
@@ -309,8 +344,15 @@ template <typename ItemT> struct Menu : Window, List
|
||||
|
||||
/// Apply filter predicate to items in the menu and show the ones for which it
|
||||
/// returned true.
|
||||
template <typename FilterPredicate>
|
||||
bool applyFilter(FilterPredicate &&p);
|
||||
template <typename PredicateT>
|
||||
void applyFilter(PredicateT &&pred);
|
||||
|
||||
/// Reapply previously applied filter.
|
||||
void reapplyFilter();
|
||||
|
||||
/// Get current filter predicate.
|
||||
template <typename TargetT>
|
||||
const TargetT *filterPredicate() const;
|
||||
|
||||
/// Clear results of applyFilter and show all items.
|
||||
void clearFilter();
|
||||
@@ -451,9 +493,10 @@ private:
|
||||
return !(*m_items)[pos].isSeparator()
|
||||
&& !(*m_items)[pos].isInactive();
|
||||
}
|
||||
|
||||
|
||||
ItemDisplayer m_item_displayer;
|
||||
|
||||
FilterPredicate m_filter_predicate;
|
||||
|
||||
std::vector<Item> *m_items;
|
||||
std::vector<Item> m_all_items;
|
||||
std::vector<Item> m_filtered_items;
|
||||
|
||||
@@ -40,7 +40,8 @@ Menu<ItemT>::Menu(size_t startx,
|
||||
Color color,
|
||||
Border border)
|
||||
: Window(startx, starty, width, height, title, std::move(color), border)
|
||||
, m_item_displayer(0)
|
||||
, m_item_displayer(nullptr)
|
||||
, m_filter_predicate(nullptr)
|
||||
, m_beginning(0)
|
||||
, m_highlight(0)
|
||||
, m_highlight_color(m_base_color)
|
||||
@@ -55,6 +56,7 @@ template <typename ItemT>
|
||||
Menu<ItemT>::Menu(const Menu &rhs)
|
||||
: Window(rhs)
|
||||
, m_item_displayer(rhs.m_item_displayer)
|
||||
, m_filter_predicate(rhs.m_filter_predicate)
|
||||
, m_beginning(rhs.m_beginning)
|
||||
, m_highlight(rhs.m_highlight)
|
||||
, m_highlight_color(rhs.m_highlight_color)
|
||||
@@ -75,7 +77,8 @@ Menu<ItemT>::Menu(const Menu &rhs)
|
||||
template <typename ItemT>
|
||||
Menu<ItemT>::Menu(Menu &&rhs)
|
||||
: Window(rhs)
|
||||
, m_item_displayer(rhs.m_item_displayer)
|
||||
, m_item_displayer(std::move(rhs.m_item_displayer))
|
||||
, m_filter_predicate(std::move(rhs.m_filter_predicate))
|
||||
, m_all_items(std::move(rhs.m_all_items))
|
||||
, m_filtered_items(std::move(rhs.m_filtered_items))
|
||||
, m_beginning(rhs.m_beginning)
|
||||
@@ -99,6 +102,7 @@ Menu<ItemT> &Menu<ItemT>::operator=(Menu rhs)
|
||||
{
|
||||
std::swap(static_cast<Window &>(*this), static_cast<Window &>(rhs));
|
||||
std::swap(m_item_displayer, rhs.m_item_displayer);
|
||||
std::swap(m_filter_predicate, rhs.m_filter_predicate);
|
||||
std::swap(m_all_items, rhs.m_all_items);
|
||||
std::swap(m_filtered_items, rhs.m_filtered_items);
|
||||
std::swap(m_beginning, rhs.m_beginning);
|
||||
@@ -117,6 +121,12 @@ Menu<ItemT> &Menu<ItemT>::operator=(Menu rhs)
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename ItemT> template <typename ItemDisplayerT>
|
||||
void Menu<ItemT>::setItemDisplayer(ItemDisplayerT &&displayer)
|
||||
{
|
||||
m_item_displayer = std::forward<ItemDisplayerT>(displayer);
|
||||
}
|
||||
|
||||
template <typename ItemT>
|
||||
void Menu<ItemT>::resizeList(size_t new_size)
|
||||
{
|
||||
@@ -326,9 +336,8 @@ void Menu<ItemT>::reset()
|
||||
template <typename ItemT>
|
||||
void Menu<ItemT>::clear()
|
||||
{
|
||||
m_all_items.clear();
|
||||
m_filtered_items.clear();
|
||||
m_items = &m_all_items;
|
||||
clearFilter();
|
||||
m_items->clear();
|
||||
}
|
||||
|
||||
template <typename ItemT>
|
||||
@@ -350,20 +359,35 @@ size_t Menu<ItemT>::choice() const
|
||||
return m_highlight;
|
||||
}
|
||||
|
||||
template <typename ItemT> template <typename FilterPredicate>
|
||||
bool Menu<ItemT>::applyFilter(FilterPredicate &&p)
|
||||
template <typename ItemT> template <typename PredicateT>
|
||||
void Menu<ItemT>::applyFilter(PredicateT &&pred)
|
||||
{
|
||||
m_filter_predicate = std::forward<PredicateT>(pred);
|
||||
m_filtered_items.clear();
|
||||
|
||||
for (const auto &item : m_all_items)
|
||||
if (p(item))
|
||||
if (m_filter_predicate(item))
|
||||
m_filtered_items.push_back(item);
|
||||
|
||||
m_items = &m_filtered_items;
|
||||
return !m_filtered_items.empty();
|
||||
}
|
||||
|
||||
template <typename ItemT>
|
||||
void Menu<ItemT>::reapplyFilter()
|
||||
{
|
||||
applyFilter(m_filter_predicate);
|
||||
}
|
||||
|
||||
template <typename ItemT> template <typename TargetT>
|
||||
const TargetT *Menu<ItemT>::filterPredicate() const
|
||||
{
|
||||
return m_filter_predicate.template target<TargetT>();
|
||||
}
|
||||
|
||||
template <typename ItemT>
|
||||
void Menu<ItemT>::clearFilter()
|
||||
{
|
||||
m_filter_predicate = nullptr;
|
||||
m_filtered_items.clear();
|
||||
m_items = &m_all_items;
|
||||
}
|
||||
|
||||
@@ -176,6 +176,27 @@ bool Playlist::search(SearchDirection direction, bool wrap, bool skip_current)
|
||||
return ::search(w, m_search_predicate, direction, wrap, skip_current);
|
||||
}
|
||||
|
||||
std::string Playlist::currentFilter()
|
||||
{
|
||||
std::string result;
|
||||
if (auto pred = w.filterPredicate<Regex::Filter<MPD::Song>>())
|
||||
result = pred->constraint();
|
||||
return result;
|
||||
}
|
||||
|
||||
void Playlist::applyFilter(const std::string &constraint)
|
||||
{
|
||||
if (!constraint.empty())
|
||||
{
|
||||
w.applyFilter(Regex::Filter<MPD::Song>(
|
||||
constraint,
|
||||
Config.regex_type,
|
||||
playlistEntryMatcher));
|
||||
}
|
||||
else
|
||||
w.clearFilter();
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool Playlist::itemAvailable()
|
||||
@@ -202,6 +223,7 @@ MPD::Song Playlist::nowPlayingSong()
|
||||
MPD::Song s;
|
||||
if (Status::State::player() != MPD::psUnknown)
|
||||
{
|
||||
ScopedUnfilteredMenu<MPD::Song, ReapplyFilter::No> sunfilter(w);
|
||||
auto sp = Status::State::currentSongPosition();
|
||||
if (sp >= 0 && size_t(sp) < w.size())
|
||||
s = w.at(sp).value();
|
||||
@@ -209,6 +231,24 @@ MPD::Song Playlist::nowPlayingSong()
|
||||
return s;
|
||||
}
|
||||
|
||||
void Playlist::moveToSong(const MPD::Song &s)
|
||||
{
|
||||
if (!w.isFiltered())
|
||||
w.highlight(s.getPosition());
|
||||
else
|
||||
{
|
||||
auto cmp = [](const MPD::Song &a, const MPD::Song &b) {
|
||||
return a.getPosition() < b.getPosition();
|
||||
};
|
||||
auto first = w.beginV(), last = w.endV();
|
||||
auto it = std::lower_bound(first, last, s, cmp);
|
||||
if (it != last && it->getPosition() == s.getPosition())
|
||||
w.highlight(it - first);
|
||||
else
|
||||
Statusbar::print("Song is filtered out");
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::enableHighlighting()
|
||||
{
|
||||
w.setHighlighting(true);
|
||||
@@ -228,6 +268,7 @@ std::string Playlist::getTotalLength()
|
||||
}
|
||||
if (Config.playlist_show_remaining_time && m_reload_remaining)
|
||||
{
|
||||
ScopedUnfilteredMenu<MPD::Song, ReapplyFilter::No> sunfilter(w);
|
||||
m_remaining_time = 0;
|
||||
for (size_t i = Status::State::currentSongPosition(); i < w.size(); ++i)
|
||||
m_remaining_time += w[i].value().getDuration();
|
||||
@@ -235,6 +276,12 @@ std::string Playlist::getTotalLength()
|
||||
}
|
||||
|
||||
result << '(' << w.size() << (w.size() == 1 ? " item" : " items");
|
||||
|
||||
if (w.isFiltered())
|
||||
{
|
||||
ScopedUnfilteredMenu<MPD::Song, ReapplyFilter::No> sunfilter(w);
|
||||
result << " (out of " << w.size() << ")";
|
||||
}
|
||||
|
||||
if (m_total_length)
|
||||
{
|
||||
@@ -243,7 +290,7 @@ std::string Playlist::getTotalLength()
|
||||
}
|
||||
if (Config.playlist_show_remaining_time && m_remaining_time && w.size() > 1)
|
||||
{
|
||||
result << " :: remaining: ";
|
||||
result << ", remaining: ";
|
||||
ShowTime(result, m_remaining_time, Config.playlist_shorten_total_times);
|
||||
}
|
||||
result << ')';
|
||||
|
||||
@@ -54,15 +54,21 @@ struct Playlist: Screen<SongMenu>, HasSongs, Searchable, Tabbable
|
||||
virtual void setSearchConstraint(const std::string &constraint) OVERRIDE;
|
||||
virtual void clearSearchConstraint() OVERRIDE;
|
||||
virtual bool search(SearchDirection direction, bool wrap, bool skip_current) OVERRIDE;
|
||||
|
||||
virtual std::string currentFilter() OVERRIDE;
|
||||
virtual void applyFilter(const std::string &filter) OVERRIDE;
|
||||
|
||||
// HasSongs implementation
|
||||
virtual bool itemAvailable() OVERRIDE;
|
||||
virtual bool addItemToPlaylist(bool play) OVERRIDE;
|
||||
virtual std::vector<MPD::Song> getSelectedSongs() OVERRIDE;
|
||||
|
||||
// private members
|
||||
// other members
|
||||
MPD::Song nowPlayingSong();
|
||||
|
||||
// Move to given song from playlist.
|
||||
void moveToSong(const MPD::Song &s);
|
||||
|
||||
void enableHighlighting();
|
||||
|
||||
void setSelectedItemsPriority(int prio);
|
||||
|
||||
@@ -96,7 +96,7 @@ struct Song
|
||||
{
|
||||
return !(operator==(rhs));
|
||||
}
|
||||
|
||||
|
||||
const char *c_uri() const { return m_song ? mpd_song_get_uri(m_song.get()) : ""; }
|
||||
|
||||
static std::string ShowTime(unsigned length);
|
||||
|
||||
@@ -408,29 +408,33 @@ int Status::State::volume()
|
||||
|
||||
void Status::Changes::playlist(unsigned previous_version)
|
||||
{
|
||||
if (m_playlist_length < myPlaylist->main().size())
|
||||
{
|
||||
auto it = myPlaylist->main().begin()+m_playlist_length;
|
||||
auto end = myPlaylist->main().end();
|
||||
for (; it != end; ++it)
|
||||
myPlaylist->unregisterSong(it->value());
|
||||
myPlaylist->main().resizeList(m_playlist_length);
|
||||
}
|
||||
ScopedUnfilteredMenu<MPD::Song, ReapplyFilter::Yes> sunfilter(myPlaylist->main());
|
||||
|
||||
MPD::SongIterator s = Mpd.GetPlaylistChanges(previous_version), end;
|
||||
for (; s != end; ++s)
|
||||
{
|
||||
size_t pos = s->getPosition();
|
||||
myPlaylist->registerSong(*s);
|
||||
if (pos < myPlaylist->main().size())
|
||||
if (m_playlist_length < myPlaylist->main().size())
|
||||
{
|
||||
// if song's already in playlist, replace it with a new one
|
||||
MPD::Song &old_s = myPlaylist->main()[pos].value();
|
||||
myPlaylist->unregisterSong(old_s);
|
||||
old_s = std::move(*s);
|
||||
auto it = myPlaylist->main().begin()+m_playlist_length;
|
||||
auto end = myPlaylist->main().end();
|
||||
for (; it != end; ++it)
|
||||
myPlaylist->unregisterSong(it->value());
|
||||
myPlaylist->main().resizeList(m_playlist_length);
|
||||
}
|
||||
|
||||
MPD::SongIterator s = Mpd.GetPlaylistChanges(previous_version), end;
|
||||
for (; s != end; ++s)
|
||||
{
|
||||
size_t pos = s->getPosition();
|
||||
myPlaylist->registerSong(*s);
|
||||
if (pos < myPlaylist->main().size())
|
||||
{
|
||||
// if song's already in playlist, replace it with a new one
|
||||
MPD::Song &old_s = myPlaylist->main()[pos].value();
|
||||
myPlaylist->unregisterSong(old_s);
|
||||
old_s = std::move(*s);
|
||||
}
|
||||
else // otherwise just add it to playlist
|
||||
myPlaylist->main().addItem(std::move(*s));
|
||||
}
|
||||
else // otherwise just add it to playlist
|
||||
myPlaylist->main().addItem(std::move(*s));
|
||||
}
|
||||
|
||||
myPlaylist->reloadTotalLength();
|
||||
@@ -576,7 +580,7 @@ void Status::Changes::songID(int song_id)
|
||||
drawTitle(s);
|
||||
|
||||
if (Config.autocenter_mode)
|
||||
pl.highlight(Status::State::currentSongPosition());
|
||||
myPlaylist->moveToSong(s);
|
||||
|
||||
if (Config.now_playing_lyrics && isVisible(myLyrics) && myLyrics->previousScreen() == myPlaylist)
|
||||
{
|
||||
|
||||
@@ -220,19 +220,34 @@ bool Statusbar::Helpers::ImmediatelyReturnOneOf::operator()(const char *s) const
|
||||
return !isOneOf(s);
|
||||
}
|
||||
|
||||
bool Statusbar::Helpers::ApplyFilterImmediately::operator()(const char *s)
|
||||
{
|
||||
using Global::myScreen;
|
||||
Status::trace();
|
||||
try {
|
||||
if (m_w->allowsSearching() && m_w->currentFilter() != s)
|
||||
{
|
||||
m_w->applyFilter(s);
|
||||
if (myScreen == myPlaylist)
|
||||
myPlaylist->enableHighlighting();
|
||||
myScreen->refreshWindow();
|
||||
}
|
||||
} catch (boost::bad_expression &) { }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Statusbar::Helpers::FindImmediately::operator()(const char *s)
|
||||
{
|
||||
using Global::myScreen;
|
||||
Status::trace();
|
||||
try {
|
||||
if (m_w->allowsSearching() && m_s != s)
|
||||
if (m_w->allowsSearching() && m_w->searchConstraint() != s)
|
||||
{
|
||||
m_w->setSearchConstraint(s);
|
||||
m_found = m_w->search(m_direction, Config.wrapped_search, false);
|
||||
m_w->search(m_direction, Config.wrapped_search, false);
|
||||
if (myScreen == myPlaylist)
|
||||
myPlaylist->enableHighlighting();
|
||||
myScreen->refreshWindow();
|
||||
m_s = s;
|
||||
}
|
||||
} catch (boost::bad_expression &) { }
|
||||
return true;
|
||||
|
||||
@@ -89,10 +89,22 @@ private:
|
||||
std::vector<std::string> m_values;
|
||||
};
|
||||
|
||||
struct ApplyFilterImmediately
|
||||
{
|
||||
ApplyFilterImmediately(Searchable *w)
|
||||
: m_w(w)
|
||||
{ }
|
||||
|
||||
bool operator()(const char *s);
|
||||
|
||||
private:
|
||||
Searchable *m_w;
|
||||
};
|
||||
|
||||
struct FindImmediately
|
||||
{
|
||||
FindImmediately(Searchable *w, SearchDirection direction)
|
||||
: m_w(w), m_direction(direction), m_found(true)
|
||||
: m_w(w), m_direction(direction)
|
||||
{ }
|
||||
|
||||
bool operator()(const char *s);
|
||||
@@ -100,8 +112,6 @@ struct FindImmediately
|
||||
private:
|
||||
Searchable *m_w;
|
||||
const SearchDirection m_direction;
|
||||
std::string m_s;
|
||||
bool m_found;
|
||||
};
|
||||
|
||||
struct TryExecuteImmediateCommand
|
||||
|
||||
Reference in New Issue
Block a user