Resurrect filtering of lists (playlist only for now)
This commit is contained in:
1
NEWS
1
NEWS
@@ -1,6 +1,7 @@
|
|||||||
ncmpcpp-0.8 (????-??-??)
|
ncmpcpp-0.8 (????-??-??)
|
||||||
* Configuration variable 'execute_on_player_state_change' was added.
|
* 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 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)
|
ncmpcpp-0.7.7 (2016-10-31)
|
||||||
* Fixed compilation on 32bit platforms.
|
* Fixed compilation on 32bit platforms.
|
||||||
|
|||||||
@@ -880,7 +880,12 @@ void MoveSelectedItemsUp::run()
|
|||||||
{
|
{
|
||||||
if (myScreen == myPlaylist)
|
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)
|
else if (myScreen == myPlaylistEditor)
|
||||||
{
|
{
|
||||||
@@ -903,7 +908,12 @@ void MoveSelectedItemsDown::run()
|
|||||||
{
|
{
|
||||||
if (myScreen == myPlaylist)
|
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)
|
else if (myScreen == myPlaylistEditor)
|
||||||
{
|
{
|
||||||
@@ -1157,7 +1167,7 @@ void TogglePlayingSongCentering::run()
|
|||||||
{
|
{
|
||||||
auto s = myPlaylist->nowPlayingSong();
|
auto s = myPlaylist->nowPlayingSong();
|
||||||
if (!s.empty())
|
if (!s.empty())
|
||||||
myPlaylist->main().highlight(s.getPosition());
|
myPlaylist->moveToSong(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1187,7 +1197,7 @@ void JumpToPlayingSong::run()
|
|||||||
return;
|
return;
|
||||||
if (myScreen == myPlaylist)
|
if (myScreen == myPlaylist)
|
||||||
{
|
{
|
||||||
myPlaylist->main().highlight(s.getPosition());
|
myPlaylist->moveToSong(s);
|
||||||
}
|
}
|
||||||
else if (myScreen == myBrowser)
|
else if (myScreen == myBrowser)
|
||||||
{
|
{
|
||||||
@@ -1936,11 +1946,47 @@ void ReversePlaylist::run()
|
|||||||
|
|
||||||
bool ApplyFilter::canBeRun()
|
bool ApplyFilter::canBeRun()
|
||||||
{
|
{
|
||||||
return false;
|
m_searchable = dynamic_cast<Searchable *>(myScreen);
|
||||||
|
return m_searchable != nullptr
|
||||||
|
&& myScreen == myPlaylist;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApplyFilter::run()
|
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()
|
bool Find::canBeRun()
|
||||||
{
|
{
|
||||||
@@ -2952,33 +2998,30 @@ void findItem(const SearchDirection direction)
|
|||||||
assert(w != nullptr);
|
assert(w != nullptr);
|
||||||
assert(w->allowsSearching());
|
assert(w->allowsSearching());
|
||||||
|
|
||||||
std::string constraint;
|
std::string constraint = w->searchConstraint();
|
||||||
{
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (constraint.empty())
|
Statusbar::ScopedLock slock;
|
||||||
{
|
NC::Window::ScopedPromptHook prompt_hook(
|
||||||
Statusbar::printf("Constraint unset");
|
*wFooter,
|
||||||
w->clearSearchConstraint();
|
Statusbar::Helpers::FindImmediately(w, direction));
|
||||||
}
|
Statusbar::put() << (boost::format("Find %1%: ") % direction).str();
|
||||||
else
|
constraint = wFooter->prompt(constraint);
|
||||||
{
|
|
||||||
w->setSearchConstraint(constraint);
|
|
||||||
Statusbar::printf("Using constraint \"%1%\"", 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()
|
void listsChangeFinisher()
|
||||||
|
|||||||
@@ -1044,6 +1044,8 @@ struct ApplyFilter: public BaseAction
|
|||||||
private:
|
private:
|
||||||
virtual bool canBeRun() OVERRIDE;
|
virtual bool canBeRun() OVERRIDE;
|
||||||
virtual void run() OVERRIDE;
|
virtual void run() OVERRIDE;
|
||||||
|
|
||||||
|
Searchable *m_searchable;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Find: BaseAction
|
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();
|
is_selected = menu.drawn()->isSelected();
|
||||||
discard_colors = Config.discard_colors_if_item_is_selected && is_selected;
|
discard_colors = Config.discard_colors_if_item_is_selected && is_selected;
|
||||||
|
|
||||||
int song_pos = drawn_pos;
|
int song_pos = s.getPosition();
|
||||||
is_now_playing = Status::State::player() != MPD::psStop && myPlaylist->isActiveWindow(menu)
|
is_now_playing = Status::State::player() != MPD::psStop
|
||||||
&& song_pos == Status::State::currentSongPosition();
|
&& myPlaylist->isActiveWindow(menu)
|
||||||
|
&& song_pos == Status::State::currentSongPosition();
|
||||||
if (is_now_playing)
|
if (is_now_playing)
|
||||||
menu << Config.now_playing_prefix;
|
menu << Config.now_playing_prefix;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,40 @@
|
|||||||
#include "utility/type_conversions.h"
|
#include "utility/type_conversions.h"
|
||||||
#include "utility/wide_string.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>
|
template <typename Iterator, typename PredicateT>
|
||||||
Iterator wrappedSearch(Iterator begin, Iterator current, Iterator end,
|
Iterator wrappedSearch(Iterator begin, Iterator current, Iterator end,
|
||||||
const PredicateT &pred, bool wrap, bool skip_current)
|
const PredicateT &pred, bool wrap, bool skip_current)
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ struct Searchable
|
|||||||
virtual void setSearchConstraint(const std::string &constraint) = 0;
|
virtual void setSearchConstraint(const std::string &constraint) = 0;
|
||||||
virtual void clearSearchConstraint() = 0;
|
virtual void clearSearchConstraint() = 0;
|
||||||
virtual bool search(SearchDirection direction, bool wrap, bool skip_current) = 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
|
struct HasActions
|
||||||
|
|||||||
159
src/menu.h
159
src/menu.h
@@ -21,7 +21,6 @@
|
|||||||
#ifndef NCMPCPP_MENU_H
|
#ifndef NCMPCPP_MENU_H
|
||||||
#define NCMPCPP_MENU_H
|
#define NCMPCPP_MENU_H
|
||||||
|
|
||||||
#include <boost/iterator/indirect_iterator.hpp>
|
|
||||||
#include <boost/iterator/transform_iterator.hpp>
|
#include <boost/iterator/transform_iterator.hpp>
|
||||||
#include <boost/range/detail/any_iterator.hpp>
|
#include <boost/range/detail/any_iterator.hpp>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
@@ -96,9 +95,6 @@ struct List
|
|||||||
bool isInactive() const { return m_properties & Inactive; }
|
bool isInactive() const { return m_properties & Inactive; }
|
||||||
bool isSeparator() const { return m_properties & Separator; }
|
bool isSeparator() const { return m_properties & Separator; }
|
||||||
|
|
||||||
protected:
|
|
||||||
unsigned properties() const { return m_properties; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
unsigned m_properties;
|
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::Iterator end(List &list) { return list.endP(); }
|
||||||
inline List::ConstIterator end(const List &list) { return list.endP(); }
|
inline List::ConstIterator end(const List &list) { return list.endP(); }
|
||||||
|
|
||||||
/// This template class is generic menu capable of
|
/// Generic menu capable of holding any std::vector compatible values.
|
||||||
/// holding any std::vector compatible values.
|
template <typename ItemT>
|
||||||
template <typename ItemT> struct Menu : Window, List
|
struct Menu: Window, List
|
||||||
{
|
{
|
||||||
struct Item : List::Properties
|
struct Item
|
||||||
{
|
{
|
||||||
template <bool Const>
|
friend struct Menu<ItemT>;
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef ItemT Type;
|
typedef ItemT Type;
|
||||||
|
|
||||||
friend struct Menu<ItemT>;
|
|
||||||
|
|
||||||
Item()
|
Item()
|
||||||
: m_value(std::make_shared<ItemT>(ItemT()))
|
: m_impl(std::make_shared<std::tuple<ItemT, Properties>>())
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
template <typename ValueT>
|
template <typename ValueT, typename PropertiesT>
|
||||||
Item(ValueT &&value_, Properties::Type properties)
|
Item(ValueT &&value_, PropertiesT properties_)
|
||||||
: Properties(properties)
|
: m_impl(
|
||||||
, m_value(std::make_shared<ItemT>(std::forward<ValueT>(value_)))
|
std::make_shared<std::tuple<ItemT, List::Properties>>(
|
||||||
|
std::forward<ValueT>(value_),
|
||||||
|
std::forward<PropertiesT>(properties_)))
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
ItemT &value() { return *m_value; }
|
ItemT &value() { return std::get<0>(*m_impl); }
|
||||||
const ItemT &value() const { return *m_value; }
|
const ItemT &value() const { return std::get<0>(*m_impl); }
|
||||||
|
|
||||||
ItemT &operator*() { return *m_value; }
|
Properties &properties() { return std::get<1>(*m_impl); }
|
||||||
const ItemT &operator*() const { return *m_value; }
|
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.
|
// Make a deep copy of Item.
|
||||||
Item copy() const {
|
Item copy() const {
|
||||||
return Item(*m_value, static_cast<Properties::Type>(properties()));
|
return Item(value(), properties());
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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()
|
static Item mkSeparator()
|
||||||
{
|
{
|
||||||
Item item;
|
Item item;
|
||||||
@@ -207,7 +245,7 @@ template <typename ItemT> struct Menu : Window, List
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<ItemT> m_value;
|
std::shared_ptr<std::tuple<ItemT, Properties>> m_impl;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef typename std::vector<Item>::iterator Iterator;
|
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<Iterator> ReverseIterator;
|
||||||
typedef std::reverse_iterator<ConstIterator> ConstReverseIterator;
|
typedef std::reverse_iterator<ConstIterator> ConstReverseIterator;
|
||||||
|
|
||||||
typedef boost::indirect_iterator<
|
typedef boost::transform_iterator<
|
||||||
Iterator,
|
typename Item::template ExtractValue<Item::Const::No>,
|
||||||
ItemT,
|
Iterator> ValueIterator;
|
||||||
boost::random_access_traversal_tag
|
typedef boost::transform_iterator<
|
||||||
> ValueIterator;
|
typename Item::template ExtractValue<Item::Const::Yes>,
|
||||||
typedef boost::indirect_iterator<
|
ConstIterator> ConstValueIterator;
|
||||||
ConstIterator,
|
|
||||||
const ItemT,
|
|
||||||
boost::random_access_traversal_tag
|
|
||||||
> ConstValueIterator;
|
|
||||||
typedef std::reverse_iterator<ValueIterator> ReverseValueIterator;
|
typedef std::reverse_iterator<ValueIterator> ReverseValueIterator;
|
||||||
typedef std::reverse_iterator<ConstValueIterator> ConstReverseValueIterator;
|
typedef std::reverse_iterator<ConstValueIterator> ConstReverseValueIterator;
|
||||||
|
|
||||||
typedef boost::transform_iterator<
|
typedef boost::transform_iterator<
|
||||||
typename Item::template PropertiesExtractor<false>,
|
typename Item::template ExtractProperties<Item::Const::No>,
|
||||||
Iterator
|
Iterator> PropertiesIterator;
|
||||||
> PropertiesIterator;
|
|
||||||
typedef boost::transform_iterator<
|
typedef boost::transform_iterator<
|
||||||
typename Item::template PropertiesExtractor<true>,
|
typename Item::template ExtractProperties<Item::Const::Yes>,
|
||||||
ConstIterator
|
ConstIterator> ConstPropertiesIterator;
|
||||||
> ConstPropertiesIterator;
|
|
||||||
|
|
||||||
/// Function helper prototype used to display each option on the screen.
|
/// Function helper prototype used to display each option on the screen.
|
||||||
/// If not set by setItemDisplayer(), menu won't display anything.
|
/// If not set by setItemDisplayer(), menu won't display anything.
|
||||||
/// @see setItemDisplayer()
|
/// @see setItemDisplayer()
|
||||||
typedef std::function<void(Menu<ItemT> &)> ItemDisplayer;
|
typedef std::function<void(Menu<ItemT> &)> ItemDisplayer;
|
||||||
|
|
||||||
|
typedef std::function<bool(const Item &)> FilterPredicate;
|
||||||
|
|
||||||
Menu();
|
Menu();
|
||||||
|
|
||||||
Menu(size_t startx, size_t starty, size_t width, size_t height,
|
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
|
/// Sets helper function that is responsible for displaying items
|
||||||
/// @param ptr function pointer that matches the ItemDisplayer prototype
|
/// @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())
|
/// Resizes the list to given size (adequate to std::vector::resize())
|
||||||
/// @param size requested size
|
/// @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
|
/// Apply filter predicate to items in the menu and show the ones for which it
|
||||||
/// returned true.
|
/// returned true.
|
||||||
template <typename FilterPredicate>
|
template <typename PredicateT>
|
||||||
bool applyFilter(FilterPredicate &&p);
|
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.
|
/// Clear results of applyFilter and show all items.
|
||||||
void clearFilter();
|
void clearFilter();
|
||||||
@@ -451,9 +493,10 @@ private:
|
|||||||
return !(*m_items)[pos].isSeparator()
|
return !(*m_items)[pos].isSeparator()
|
||||||
&& !(*m_items)[pos].isInactive();
|
&& !(*m_items)[pos].isInactive();
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemDisplayer m_item_displayer;
|
ItemDisplayer m_item_displayer;
|
||||||
|
FilterPredicate m_filter_predicate;
|
||||||
|
|
||||||
std::vector<Item> *m_items;
|
std::vector<Item> *m_items;
|
||||||
std::vector<Item> m_all_items;
|
std::vector<Item> m_all_items;
|
||||||
std::vector<Item> m_filtered_items;
|
std::vector<Item> m_filtered_items;
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ Menu<ItemT>::Menu(size_t startx,
|
|||||||
Color color,
|
Color color,
|
||||||
Border border)
|
Border border)
|
||||||
: Window(startx, starty, width, height, title, std::move(color), 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_beginning(0)
|
||||||
, m_highlight(0)
|
, m_highlight(0)
|
||||||
, m_highlight_color(m_base_color)
|
, m_highlight_color(m_base_color)
|
||||||
@@ -55,6 +56,7 @@ template <typename ItemT>
|
|||||||
Menu<ItemT>::Menu(const Menu &rhs)
|
Menu<ItemT>::Menu(const Menu &rhs)
|
||||||
: Window(rhs)
|
: Window(rhs)
|
||||||
, m_item_displayer(rhs.m_item_displayer)
|
, m_item_displayer(rhs.m_item_displayer)
|
||||||
|
, m_filter_predicate(rhs.m_filter_predicate)
|
||||||
, m_beginning(rhs.m_beginning)
|
, m_beginning(rhs.m_beginning)
|
||||||
, m_highlight(rhs.m_highlight)
|
, m_highlight(rhs.m_highlight)
|
||||||
, m_highlight_color(rhs.m_highlight_color)
|
, m_highlight_color(rhs.m_highlight_color)
|
||||||
@@ -75,7 +77,8 @@ Menu<ItemT>::Menu(const Menu &rhs)
|
|||||||
template <typename ItemT>
|
template <typename ItemT>
|
||||||
Menu<ItemT>::Menu(Menu &&rhs)
|
Menu<ItemT>::Menu(Menu &&rhs)
|
||||||
: Window(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_all_items(std::move(rhs.m_all_items))
|
||||||
, m_filtered_items(std::move(rhs.m_filtered_items))
|
, m_filtered_items(std::move(rhs.m_filtered_items))
|
||||||
, m_beginning(rhs.m_beginning)
|
, 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(static_cast<Window &>(*this), static_cast<Window &>(rhs));
|
||||||
std::swap(m_item_displayer, rhs.m_item_displayer);
|
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_all_items, rhs.m_all_items);
|
||||||
std::swap(m_filtered_items, rhs.m_filtered_items);
|
std::swap(m_filtered_items, rhs.m_filtered_items);
|
||||||
std::swap(m_beginning, rhs.m_beginning);
|
std::swap(m_beginning, rhs.m_beginning);
|
||||||
@@ -117,6 +121,12 @@ Menu<ItemT> &Menu<ItemT>::operator=(Menu rhs)
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename ItemT> template <typename ItemDisplayerT>
|
||||||
|
void Menu<ItemT>::setItemDisplayer(ItemDisplayerT &&displayer)
|
||||||
|
{
|
||||||
|
m_item_displayer = std::forward<ItemDisplayerT>(displayer);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename ItemT>
|
template <typename ItemT>
|
||||||
void Menu<ItemT>::resizeList(size_t new_size)
|
void Menu<ItemT>::resizeList(size_t new_size)
|
||||||
{
|
{
|
||||||
@@ -326,9 +336,8 @@ void Menu<ItemT>::reset()
|
|||||||
template <typename ItemT>
|
template <typename ItemT>
|
||||||
void Menu<ItemT>::clear()
|
void Menu<ItemT>::clear()
|
||||||
{
|
{
|
||||||
m_all_items.clear();
|
clearFilter();
|
||||||
m_filtered_items.clear();
|
m_items->clear();
|
||||||
m_items = &m_all_items;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ItemT>
|
template <typename ItemT>
|
||||||
@@ -350,20 +359,35 @@ size_t Menu<ItemT>::choice() const
|
|||||||
return m_highlight;
|
return m_highlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ItemT> template <typename FilterPredicate>
|
template <typename ItemT> template <typename PredicateT>
|
||||||
bool Menu<ItemT>::applyFilter(FilterPredicate &&p)
|
void Menu<ItemT>::applyFilter(PredicateT &&pred)
|
||||||
{
|
{
|
||||||
|
m_filter_predicate = std::forward<PredicateT>(pred);
|
||||||
m_filtered_items.clear();
|
m_filtered_items.clear();
|
||||||
|
|
||||||
for (const auto &item : m_all_items)
|
for (const auto &item : m_all_items)
|
||||||
if (p(item))
|
if (m_filter_predicate(item))
|
||||||
m_filtered_items.push_back(item);
|
m_filtered_items.push_back(item);
|
||||||
|
|
||||||
m_items = &m_filtered_items;
|
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>
|
template <typename ItemT>
|
||||||
void Menu<ItemT>::clearFilter()
|
void Menu<ItemT>::clearFilter()
|
||||||
{
|
{
|
||||||
|
m_filter_predicate = nullptr;
|
||||||
m_filtered_items.clear();
|
m_filtered_items.clear();
|
||||||
m_items = &m_all_items;
|
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);
|
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()
|
bool Playlist::itemAvailable()
|
||||||
@@ -202,6 +223,7 @@ MPD::Song Playlist::nowPlayingSong()
|
|||||||
MPD::Song s;
|
MPD::Song s;
|
||||||
if (Status::State::player() != MPD::psUnknown)
|
if (Status::State::player() != MPD::psUnknown)
|
||||||
{
|
{
|
||||||
|
ScopedUnfilteredMenu<MPD::Song, ReapplyFilter::No> sunfilter(w);
|
||||||
auto sp = Status::State::currentSongPosition();
|
auto sp = Status::State::currentSongPosition();
|
||||||
if (sp >= 0 && size_t(sp) < w.size())
|
if (sp >= 0 && size_t(sp) < w.size())
|
||||||
s = w.at(sp).value();
|
s = w.at(sp).value();
|
||||||
@@ -209,6 +231,24 @@ MPD::Song Playlist::nowPlayingSong()
|
|||||||
return s;
|
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()
|
void Playlist::enableHighlighting()
|
||||||
{
|
{
|
||||||
w.setHighlighting(true);
|
w.setHighlighting(true);
|
||||||
@@ -228,6 +268,7 @@ std::string Playlist::getTotalLength()
|
|||||||
}
|
}
|
||||||
if (Config.playlist_show_remaining_time && m_reload_remaining)
|
if (Config.playlist_show_remaining_time && m_reload_remaining)
|
||||||
{
|
{
|
||||||
|
ScopedUnfilteredMenu<MPD::Song, ReapplyFilter::No> sunfilter(w);
|
||||||
m_remaining_time = 0;
|
m_remaining_time = 0;
|
||||||
for (size_t i = Status::State::currentSongPosition(); i < w.size(); ++i)
|
for (size_t i = Status::State::currentSongPosition(); i < w.size(); ++i)
|
||||||
m_remaining_time += w[i].value().getDuration();
|
m_remaining_time += w[i].value().getDuration();
|
||||||
@@ -235,6 +276,12 @@ std::string Playlist::getTotalLength()
|
|||||||
}
|
}
|
||||||
|
|
||||||
result << '(' << w.size() << (w.size() == 1 ? " item" : " items");
|
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)
|
if (m_total_length)
|
||||||
{
|
{
|
||||||
@@ -243,7 +290,7 @@ std::string Playlist::getTotalLength()
|
|||||||
}
|
}
|
||||||
if (Config.playlist_show_remaining_time && m_remaining_time && w.size() > 1)
|
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);
|
ShowTime(result, m_remaining_time, Config.playlist_shorten_total_times);
|
||||||
}
|
}
|
||||||
result << ')';
|
result << ')';
|
||||||
|
|||||||
@@ -54,15 +54,21 @@ struct Playlist: Screen<SongMenu>, HasSongs, Searchable, Tabbable
|
|||||||
virtual void setSearchConstraint(const std::string &constraint) OVERRIDE;
|
virtual void setSearchConstraint(const std::string &constraint) OVERRIDE;
|
||||||
virtual void clearSearchConstraint() OVERRIDE;
|
virtual void clearSearchConstraint() OVERRIDE;
|
||||||
virtual bool search(SearchDirection direction, bool wrap, bool skip_current) 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
|
// HasSongs implementation
|
||||||
virtual bool itemAvailable() OVERRIDE;
|
virtual bool itemAvailable() OVERRIDE;
|
||||||
virtual bool addItemToPlaylist(bool play) OVERRIDE;
|
virtual bool addItemToPlaylist(bool play) OVERRIDE;
|
||||||
virtual std::vector<MPD::Song> getSelectedSongs() OVERRIDE;
|
virtual std::vector<MPD::Song> getSelectedSongs() OVERRIDE;
|
||||||
|
|
||||||
// private members
|
// other members
|
||||||
MPD::Song nowPlayingSong();
|
MPD::Song nowPlayingSong();
|
||||||
|
|
||||||
|
// Move to given song from playlist.
|
||||||
|
void moveToSong(const MPD::Song &s);
|
||||||
|
|
||||||
void enableHighlighting();
|
void enableHighlighting();
|
||||||
|
|
||||||
void setSelectedItemsPriority(int prio);
|
void setSelectedItemsPriority(int prio);
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ struct Song
|
|||||||
{
|
{
|
||||||
return !(operator==(rhs));
|
return !(operator==(rhs));
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *c_uri() const { return m_song ? mpd_song_get_uri(m_song.get()) : ""; }
|
const char *c_uri() const { return m_song ? mpd_song_get_uri(m_song.get()) : ""; }
|
||||||
|
|
||||||
static std::string ShowTime(unsigned length);
|
static std::string ShowTime(unsigned length);
|
||||||
|
|||||||
@@ -408,29 +408,33 @@ int Status::State::volume()
|
|||||||
|
|
||||||
void Status::Changes::playlist(unsigned previous_version)
|
void Status::Changes::playlist(unsigned previous_version)
|
||||||
{
|
{
|
||||||
if (m_playlist_length < myPlaylist->main().size())
|
|
||||||
{
|
{
|
||||||
auto it = myPlaylist->main().begin()+m_playlist_length;
|
ScopedUnfilteredMenu<MPD::Song, ReapplyFilter::Yes> sunfilter(myPlaylist->main());
|
||||||
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;
|
if (m_playlist_length < myPlaylist->main().size())
|
||||||
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
|
auto it = myPlaylist->main().begin()+m_playlist_length;
|
||||||
MPD::Song &old_s = myPlaylist->main()[pos].value();
|
auto end = myPlaylist->main().end();
|
||||||
myPlaylist->unregisterSong(old_s);
|
for (; it != end; ++it)
|
||||||
old_s = std::move(*s);
|
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();
|
myPlaylist->reloadTotalLength();
|
||||||
@@ -576,7 +580,7 @@ void Status::Changes::songID(int song_id)
|
|||||||
drawTitle(s);
|
drawTitle(s);
|
||||||
|
|
||||||
if (Config.autocenter_mode)
|
if (Config.autocenter_mode)
|
||||||
pl.highlight(Status::State::currentSongPosition());
|
myPlaylist->moveToSong(s);
|
||||||
|
|
||||||
if (Config.now_playing_lyrics && isVisible(myLyrics) && myLyrics->previousScreen() == myPlaylist)
|
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);
|
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)
|
bool Statusbar::Helpers::FindImmediately::operator()(const char *s)
|
||||||
{
|
{
|
||||||
using Global::myScreen;
|
using Global::myScreen;
|
||||||
Status::trace();
|
Status::trace();
|
||||||
try {
|
try {
|
||||||
if (m_w->allowsSearching() && m_s != s)
|
if (m_w->allowsSearching() && m_w->searchConstraint() != s)
|
||||||
{
|
{
|
||||||
m_w->setSearchConstraint(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)
|
if (myScreen == myPlaylist)
|
||||||
myPlaylist->enableHighlighting();
|
myPlaylist->enableHighlighting();
|
||||||
myScreen->refreshWindow();
|
myScreen->refreshWindow();
|
||||||
m_s = s;
|
|
||||||
}
|
}
|
||||||
} catch (boost::bad_expression &) { }
|
} catch (boost::bad_expression &) { }
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -89,10 +89,22 @@ private:
|
|||||||
std::vector<std::string> m_values;
|
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
|
struct FindImmediately
|
||||||
{
|
{
|
||||||
FindImmediately(Searchable *w, SearchDirection direction)
|
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);
|
bool operator()(const char *s);
|
||||||
@@ -100,8 +112,6 @@ struct FindImmediately
|
|||||||
private:
|
private:
|
||||||
Searchable *m_w;
|
Searchable *m_w;
|
||||||
const SearchDirection m_direction;
|
const SearchDirection m_direction;
|
||||||
std::string m_s;
|
|
||||||
bool m_found;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TryExecuteImmediateCommand
|
struct TryExecuteImmediateCommand
|
||||||
|
|||||||
Reference in New Issue
Block a user