actions: add support for range selection and make a few actions work on ranges
This commit is contained in:
1
NEWS
1
NEWS
@@ -26,6 +26,7 @@ ncmpcpp-0.7 (????-??-??)
|
|||||||
* Searching in text fields now respects regular expression configuration.
|
* Searching in text fields now respects regular expression configuration.
|
||||||
* Monolithic 'press_space' action was split into 'add_item_to_playlist', 'toggle_lyrics_update_on_song_change' and 'toggle_visualization_type'.
|
* Monolithic 'press_space' action was split into 'add_item_to_playlist', 'toggle_lyrics_update_on_song_change' and 'toggle_visualization_type'.
|
||||||
* Sorting actions were rebound to Ctrl-S.
|
* Sorting actions were rebound to Ctrl-S.
|
||||||
|
* Support for range selection was added (bound to Ctrl-V by default). In addition, sorting, reversing and shuffling items in playlist now works on ranges.
|
||||||
|
|
||||||
ncmpcpp-0.6.4 (2015-05-02)
|
ncmpcpp-0.6.4 (2015-05-02)
|
||||||
|
|
||||||
|
|||||||
@@ -415,6 +415,9 @@
|
|||||||
#def_key "l"
|
#def_key "l"
|
||||||
# show_lyrics
|
# show_lyrics
|
||||||
#
|
#
|
||||||
|
#def_key "ctrl_v"
|
||||||
|
# select_range
|
||||||
|
#
|
||||||
#def_key "v"
|
#def_key "v"
|
||||||
# reverse_selection
|
# reverse_selection
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -89,6 +89,15 @@ void seek();
|
|||||||
void findItem(const SearchDirection direction);
|
void findItem(const SearchDirection direction);
|
||||||
void listsChangeFinisher();
|
void listsChangeFinisher();
|
||||||
|
|
||||||
|
template <typename Iterator>
|
||||||
|
bool findSelectedRangeAndPrintInfoIfNot(Iterator &first, Iterator &last)
|
||||||
|
{
|
||||||
|
bool success = findSelectedRange(first, last);
|
||||||
|
if (!success)
|
||||||
|
Statusbar::print("No range selected");
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Actions {
|
namespace Actions {
|
||||||
@@ -1173,11 +1182,19 @@ void ToggleRepeat::run()
|
|||||||
Mpd.SetRepeat(!Status::State::repeat());
|
Mpd.SetRepeat(!Status::State::repeat());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Shuffle::canBeRun()
|
||||||
|
{
|
||||||
|
if (myScreen != myPlaylist)
|
||||||
|
return false;
|
||||||
|
m_begin = myPlaylist->main().begin();
|
||||||
|
m_end = myPlaylist->main().end();
|
||||||
|
return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
|
||||||
|
}
|
||||||
|
|
||||||
void Shuffle::run()
|
void Shuffle::run()
|
||||||
{
|
{
|
||||||
auto begin = myPlaylist->main().begin(), end = myPlaylist->main().end();
|
auto begin = myPlaylist->main().begin();
|
||||||
auto range = getSelectedRange(begin, end);
|
Mpd.ShuffleRange(m_begin-begin, m_end-begin);
|
||||||
Mpd.ShuffleRange(range.first-begin, range.second-begin);
|
|
||||||
Statusbar::print("Range shuffled");
|
Statusbar::print("Range shuffled");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1654,6 +1671,23 @@ void SelectItem::run()
|
|||||||
current->setSelected(!current->isSelected());
|
current->setSelected(!current->isSelected());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SelectRange::canBeRun()
|
||||||
|
{
|
||||||
|
m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
|
||||||
|
if (m_list == nullptr)
|
||||||
|
return false;
|
||||||
|
m_begin = m_list->beginP();
|
||||||
|
m_end = m_list->endP();
|
||||||
|
return findRange(m_begin, m_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelectRange::run()
|
||||||
|
{
|
||||||
|
for (; m_begin != m_end; ++m_begin)
|
||||||
|
m_begin->setSelected(true);
|
||||||
|
Statusbar::print("Range selected");
|
||||||
|
}
|
||||||
|
|
||||||
bool ReverseSelection::canBeRun()
|
bool ReverseSelection::canBeRun()
|
||||||
{
|
{
|
||||||
m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
|
m_list = dynamic_cast<NC::List *>(myScreen->activeWindow());
|
||||||
@@ -1793,7 +1827,10 @@ void ClearPlaylist::run()
|
|||||||
|
|
||||||
bool SortPlaylist::canBeRun()
|
bool SortPlaylist::canBeRun()
|
||||||
{
|
{
|
||||||
return myScreen == myPlaylist;
|
if (myScreen != myPlaylist)
|
||||||
|
return false;
|
||||||
|
auto first = myPlaylist->main().begin(), last = myPlaylist->main().end();
|
||||||
|
return findSelectedRangeAndPrintInfoIfNot(first, last);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SortPlaylist::run()
|
void SortPlaylist::run()
|
||||||
@@ -1803,12 +1840,21 @@ void SortPlaylist::run()
|
|||||||
|
|
||||||
bool ReversePlaylist::canBeRun()
|
bool ReversePlaylist::canBeRun()
|
||||||
{
|
{
|
||||||
return myScreen == myPlaylist;
|
if (myScreen != myPlaylist)
|
||||||
|
return false;
|
||||||
|
m_begin = myPlaylist->main().begin();
|
||||||
|
m_end = myPlaylist->main().end();
|
||||||
|
return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReversePlaylist::run()
|
void ReversePlaylist::run()
|
||||||
{
|
{
|
||||||
myPlaylist->Reverse();
|
Statusbar::print("Reversing range...");
|
||||||
|
Mpd.StartCommandsList();
|
||||||
|
for (--m_end; m_begin < m_end; ++m_begin, --m_end)
|
||||||
|
Mpd.Swap(m_begin->value().getPosition(), m_end->value().getPosition());
|
||||||
|
Mpd.CommitCommandsList();
|
||||||
|
Statusbar::print("Range reversed");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Find::canBeRun()
|
bool Find::canBeRun()
|
||||||
@@ -2512,6 +2558,7 @@ void populateActions()
|
|||||||
insert_action(new Actions::JumpToParentDirectory());
|
insert_action(new Actions::JumpToParentDirectory());
|
||||||
insert_action(new Actions::PressEnter());
|
insert_action(new Actions::PressEnter());
|
||||||
insert_action(new Actions::SelectItem());
|
insert_action(new Actions::SelectItem());
|
||||||
|
insert_action(new Actions::SelectRange());
|
||||||
insert_action(new Actions::PreviousColumn());
|
insert_action(new Actions::PreviousColumn());
|
||||||
insert_action(new Actions::NextColumn());
|
insert_action(new Actions::NextColumn());
|
||||||
insert_action(new Actions::MasterScreen());
|
insert_action(new Actions::MasterScreen());
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ enum class Type
|
|||||||
SetCrossfade, SetVolume, EditSong, EditLibraryTag, EditLibraryAlbum, EditDirectoryName,
|
SetCrossfade, SetVolume, EditSong, EditLibraryTag, EditLibraryAlbum, EditDirectoryName,
|
||||||
EditPlaylistName, EditLyrics, JumpToBrowser, JumpToMediaLibrary,
|
EditPlaylistName, EditLyrics, JumpToBrowser, JumpToMediaLibrary,
|
||||||
JumpToPlaylistEditor, ToggleScreenLock, JumpToTagEditor, JumpToPositionInSong,
|
JumpToPlaylistEditor, ToggleScreenLock, JumpToTagEditor, JumpToPositionInSong,
|
||||||
SelectItem, ReverseSelection, RemoveSelection, SelectAlbum, AddSelectedItems,
|
SelectItem, SelectRange, ReverseSelection, RemoveSelection, SelectAlbum, AddSelectedItems,
|
||||||
CropMainPlaylist, CropPlaylist, ClearMainPlaylist, ClearPlaylist, SortPlaylist,
|
CropMainPlaylist, CropPlaylist, ClearMainPlaylist, ClearPlaylist, SortPlaylist,
|
||||||
ReversePlaylist, Find, FindItemForward, FindItemBackward,
|
ReversePlaylist, Find, FindItemForward, FindItemBackward,
|
||||||
NextFoundItem, PreviousFoundItem, ToggleFindMode, ToggleReplayGainMode,
|
NextFoundItem, PreviousFoundItem, ToggleFindMode, ToggleReplayGainMode,
|
||||||
@@ -581,7 +581,11 @@ struct Shuffle: BaseAction
|
|||||||
Shuffle(): BaseAction(Type::Shuffle, "shuffle") { }
|
Shuffle(): BaseAction(Type::Shuffle, "shuffle") { }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
virtual bool canBeRun() OVERRIDE;
|
||||||
virtual void run() OVERRIDE;
|
virtual void run() OVERRIDE;
|
||||||
|
|
||||||
|
NC::Menu<MPD::Song>::ConstIterator m_begin;
|
||||||
|
NC::Menu<MPD::Song>::ConstIterator m_end;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ToggleRandom: BaseAction
|
struct ToggleRandom: BaseAction
|
||||||
@@ -776,6 +780,19 @@ private:
|
|||||||
NC::List *m_list;
|
NC::List *m_list;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SelectRange: BaseAction
|
||||||
|
{
|
||||||
|
SelectRange(): BaseAction(Type::SelectRange, "select_range") { }
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual bool canBeRun() OVERRIDE;
|
||||||
|
virtual void run() OVERRIDE;
|
||||||
|
|
||||||
|
NC::List *m_list;
|
||||||
|
NC::List::Iterator m_begin;
|
||||||
|
NC::List::Iterator m_end;
|
||||||
|
};
|
||||||
|
|
||||||
struct ReverseSelection: BaseAction
|
struct ReverseSelection: BaseAction
|
||||||
{
|
{
|
||||||
ReverseSelection(): BaseAction(Type::ReverseSelection, "reverse_selection") { }
|
ReverseSelection(): BaseAction(Type::ReverseSelection, "reverse_selection") { }
|
||||||
@@ -869,6 +886,9 @@ struct ReversePlaylist: BaseAction
|
|||||||
private:
|
private:
|
||||||
virtual bool canBeRun() OVERRIDE;
|
virtual bool canBeRun() OVERRIDE;
|
||||||
virtual void run() OVERRIDE;
|
virtual void run() OVERRIDE;
|
||||||
|
|
||||||
|
NC::Menu<MPD::Song>::ConstIterator m_begin;
|
||||||
|
NC::Menu<MPD::Song>::ConstIterator m_end;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Find: BaseAction
|
struct Find: BaseAction
|
||||||
|
|||||||
@@ -630,6 +630,8 @@ void BindingsConfiguration::generateDefaults()
|
|||||||
bind(k, Actions::Type::JumpToPositionInSong);
|
bind(k, Actions::Type::JumpToPositionInSong);
|
||||||
if (notBound(k = stringToKey("l")))
|
if (notBound(k = stringToKey("l")))
|
||||||
bind(k, Actions::Type::ShowLyrics);
|
bind(k, Actions::Type::ShowLyrics);
|
||||||
|
if (notBound(k = stringToKey("ctrl_v")))
|
||||||
|
bind(k, Actions::Type::SelectRange);
|
||||||
if (notBound(k = stringToKey("v")))
|
if (notBound(k = stringToKey("v")))
|
||||||
bind(k, Actions::Type::ReverseSelection);
|
bind(k, Actions::Type::ReverseSelection);
|
||||||
if (notBound(k = stringToKey("V")))
|
if (notBound(k = stringToKey("V")))
|
||||||
|
|||||||
@@ -178,6 +178,7 @@ void write_bindings(NC::Scrollpad &w)
|
|||||||
w << '\n';
|
w << '\n';
|
||||||
key(w, Type::ToggleAddMode, "Toggle add mode (add or remove/always add)");
|
key(w, Type::ToggleAddMode, "Toggle add mode (add or remove/always add)");
|
||||||
key(w, Type::ToggleMouse, "Toggle mouse support");
|
key(w, Type::ToggleMouse, "Toggle mouse support");
|
||||||
|
key(w, Type::SelectRange, "Select range");
|
||||||
key(w, Type::ReverseSelection, "Reverse selection");
|
key(w, Type::ReverseSelection, "Reverse selection");
|
||||||
key(w, Type::RemoveSelection, "Remove selection");
|
key(w, Type::RemoveSelection, "Remove selection");
|
||||||
key(w, Type::SelectItem, "Select current item");
|
key(w, Type::SelectItem, "Select current item");
|
||||||
@@ -191,7 +192,6 @@ void write_bindings(NC::Scrollpad &w)
|
|||||||
key(w, Type::ToggleConsume, "Toggle consume mode");
|
key(w, Type::ToggleConsume, "Toggle consume mode");
|
||||||
key(w, Type::ToggleReplayGainMode, "Toggle replay gain mode");
|
key(w, Type::ToggleReplayGainMode, "Toggle replay gain mode");
|
||||||
key(w, Type::ToggleBitrateVisibility, "Toggle bitrate visibility");
|
key(w, Type::ToggleBitrateVisibility, "Toggle bitrate visibility");
|
||||||
key(w, Type::Shuffle, "Shuffle selected range in playlist");
|
|
||||||
key(w, Type::ToggleCrossfade, "Toggle crossfade mode");
|
key(w, Type::ToggleCrossfade, "Toggle crossfade mode");
|
||||||
key(w, Type::SetCrossfade, "Set crossfade");
|
key(w, Type::SetCrossfade, "Set crossfade");
|
||||||
key(w, Type::SetVolume, "Set volume");
|
key(w, Type::SetVolume, "Set volume");
|
||||||
@@ -239,8 +239,9 @@ void write_bindings(NC::Scrollpad &w)
|
|||||||
key(w, Type::EditSong, "Edit song");
|
key(w, Type::EditSong, "Edit song");
|
||||||
# endif // HAVE_TAGLIB_H
|
# endif // HAVE_TAGLIB_H
|
||||||
key(w, Type::SavePlaylist, "Save playlist");
|
key(w, Type::SavePlaylist, "Save playlist");
|
||||||
key(w, Type::SortPlaylist, "Sort playlist");
|
key(w, Type::Shuffle, "Shuffle range");
|
||||||
key(w, Type::ReversePlaylist, "Reverse playlist");
|
key(w, Type::SortPlaylist, "Sort range");
|
||||||
|
key(w, Type::ReversePlaylist, "Reverse range");
|
||||||
key(w, Type::JumpToPlayingSong, "Jump to current song");
|
key(w, Type::JumpToPlayingSong, "Jump to current song");
|
||||||
key(w, Type::TogglePlayingSongCentering, "Toggle playing song centering");
|
key(w, Type::TogglePlayingSongCentering, "Toggle playing song centering");
|
||||||
|
|
||||||
|
|||||||
@@ -113,21 +113,40 @@ std::vector<Iterator> getSelected(Iterator first, Iterator last)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @return selected range within given range or original range if no item is selected
|
/// @return true if range that begins and ends with selected items was found
|
||||||
template <typename Iterator>
|
template <typename Iterator>
|
||||||
std::pair<Iterator, Iterator> getSelectedRange(Iterator first, Iterator second)
|
bool findRange(Iterator &first, Iterator &last)
|
||||||
{
|
{
|
||||||
auto result = std::make_pair(first, second);
|
for (; first != last; ++first)
|
||||||
if (hasSelected(first, second))
|
|
||||||
{
|
{
|
||||||
while (!result.first->isSelected())
|
if (first->isSelected())
|
||||||
++result.first;
|
break;
|
||||||
do
|
|
||||||
--result.second;
|
|
||||||
while (!result.second->isSelected());
|
|
||||||
++result.second;
|
|
||||||
}
|
}
|
||||||
return result;
|
if (first == last)
|
||||||
|
return false;
|
||||||
|
--last;
|
||||||
|
for (; first != last; --last)
|
||||||
|
{
|
||||||
|
if (last->isSelected())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++last;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return true if fully selected range was found, false otherwise.
|
||||||
|
template <typename Iterator>
|
||||||
|
bool findSelectedRange(Iterator &first, Iterator &last)
|
||||||
|
{
|
||||||
|
if (!findRange(first, last))
|
||||||
|
return false;
|
||||||
|
// we have range, now check if it's filled with selected items
|
||||||
|
for (auto it = first; it != last; ++it)
|
||||||
|
{
|
||||||
|
if (!it->isSelected())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|||||||
@@ -197,18 +197,6 @@ MPD::Song Playlist::nowPlayingSong()
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Playlist::Reverse()
|
|
||||||
{
|
|
||||||
Statusbar::print("Reversing playlist order...");
|
|
||||||
auto begin = w.begin(), end = w.end();
|
|
||||||
std::tie(begin, end) = getSelectedRange(begin, end);
|
|
||||||
Mpd.StartCommandsList();
|
|
||||||
for (--end; begin < end; ++begin, --end)
|
|
||||||
Mpd.Swap(begin->value().getPosition(), end->value().getPosition());
|
|
||||||
Mpd.CommitCommandsList();
|
|
||||||
Statusbar::print("Playlist reversed");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Playlist::EnableHighlighting()
|
void Playlist::EnableHighlighting()
|
||||||
{
|
{
|
||||||
w.setHighlighting(true);
|
w.setHighlighting(true);
|
||||||
|
|||||||
@@ -62,8 +62,7 @@ struct Playlist: Screen<SongMenu>, HasSongs, Searchable, Tabbable
|
|||||||
// private members
|
// private members
|
||||||
MPD::Song nowPlayingSong();
|
MPD::Song nowPlayingSong();
|
||||||
|
|
||||||
void Reverse();
|
|
||||||
|
|
||||||
void EnableHighlighting();
|
void EnableHighlighting();
|
||||||
|
|
||||||
void SetSelectedItemsPriority(int prio);
|
void SetSelectedItemsPriority(int prio);
|
||||||
|
|||||||
@@ -156,8 +156,9 @@ void SortPlaylistDialog::sort() const
|
|||||||
{
|
{
|
||||||
auto &pl = myPlaylist->main();
|
auto &pl = myPlaylist->main();
|
||||||
auto begin = pl.begin(), end = pl.end();
|
auto begin = pl.begin(), end = pl.end();
|
||||||
std::tie(begin, end) = getSelectedRange(begin, end);
|
if (!findSelectedRange(begin, end))
|
||||||
|
return;
|
||||||
|
|
||||||
size_t start_pos = begin - pl.begin();
|
size_t start_pos = begin - pl.begin();
|
||||||
std::vector<MPD::Song> playlist;
|
std::vector<MPD::Song> playlist;
|
||||||
playlist.reserve(end - begin);
|
playlist.reserve(end - begin);
|
||||||
@@ -203,7 +204,7 @@ void SortPlaylistDialog::sort() const
|
|||||||
Mpd.StartCommandsList();
|
Mpd.StartCommandsList();
|
||||||
quick_sort(playlist.begin(), playlist.end());
|
quick_sort(playlist.begin(), playlist.end());
|
||||||
Mpd.CommitCommandsList();
|
Mpd.CommitCommandsList();
|
||||||
Statusbar::print("Playlist sorted");
|
Statusbar::print("Range sorted");
|
||||||
switchToPreviousScreen();
|
switchToPreviousScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user