window: enable nonl and use raw terminal mode
[ncmpcpp.git] / src / playlist.cpp
blobf6828ce48368e9d2cb59815a23e5f911eb6a2a19
1 /***************************************************************************
2 * Copyright (C) 2008-2014 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include <algorithm>
22 #include <boost/bind.hpp>
23 #include <sstream>
25 #include "display.h"
26 #include "global.h"
27 #include "helpers.h"
28 #include "menu.h"
29 #include "playlist.h"
30 #include "regex_filter.h"
31 #include "screen_switcher.h"
32 #include "song.h"
33 #include "status.h"
34 #include "statusbar.h"
35 #include "utility/comparators.h"
36 #include "title.h"
38 using Global::MainHeight;
39 using Global::MainStartY;
41 Playlist *myPlaylist;
43 namespace {
45 std::string songToString(const MPD::Song &s);
46 bool playlistEntryMatcher(const boost::regex &rx, const MPD::Song &s);
50 Playlist::Playlist()
51 : m_total_length(0), m_remaining_time(0), m_scroll_begin(0)
52 , m_reload_total_length(false), m_reload_remaining(false)
54 w = NC::Menu<MPD::Song>(0, MainStartY, COLS, MainHeight, Config.playlist_display_mode == DisplayMode::Columns && Config.titles_visibility ? Display::Columns(COLS) : "", Config.main_color, NC::Border::None);
55 w.cyclicScrolling(Config.use_cyclic_scrolling);
56 w.centeredCursor(Config.centered_cursor);
57 w.setHighlightColor(Config.main_highlight_color);
58 w.setSelectedPrefix(Config.selected_item_prefix);
59 w.setSelectedSuffix(Config.selected_item_suffix);
60 switch (Config.playlist_display_mode)
62 case DisplayMode::Classic:
63 w.setItemDisplayer(boost::bind(Display::Songs, _1, proxySongList(), Config.song_list_format));
64 break;
65 case DisplayMode::Columns:
66 w.setItemDisplayer(boost::bind(Display::SongsInColumns, _1, proxySongList()));
67 break;
71 void Playlist::switchTo()
73 SwitchTo::execute(this);
74 m_scroll_begin = 0;
75 EnableHighlighting();
76 drawHeader();
79 void Playlist::resize()
81 size_t x_offset, width;
82 getWindowResizeParams(x_offset, width);
83 w.resize(width, MainHeight);
84 w.moveTo(x_offset, MainStartY);
86 switch (Config.playlist_display_mode)
88 case DisplayMode::Columns:
89 if (Config.titles_visibility)
91 w.setTitle(Display::Columns(w.getWidth()));
92 break;
94 case DisplayMode::Classic:
95 w.setTitle("");
98 hasToBeResized = 0;
101 std::wstring Playlist::title()
103 std::wstring result = L"Playlist ";
104 if (m_reload_total_length || m_reload_remaining)
105 m_stats = getTotalLength();
106 result += Scroller(ToWString(m_stats), m_scroll_begin, COLS-result.length()-(Config.design == Design::Alternative ? 2 : Global::VolumeState.length()));
107 return result;
110 void Playlist::update()
112 if (w.isHighlighted()
113 && Config.playlist_disable_highlight_delay.time_duration::seconds() > 0
114 && Global::Timer - m_timer > Config.playlist_disable_highlight_delay)
116 w.setHighlighting(false);
117 w.refresh();
121 void Playlist::enterPressed()
123 if (!w.empty())
124 Mpd.PlayID(w.current().value().getID());
127 void Playlist::spacePressed()
129 if (!w.empty())
131 w.current().setSelected(!w.current().isSelected());
132 w.scroll(NC::Scroll::Down);
136 void Playlist::mouseButtonPressed(MEVENT me)
138 if (!w.empty() && w.hasCoords(me.x, me.y))
140 if (size_t(me.y) < w.size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
142 w.Goto(me.y);
143 if (me.bstate & BUTTON3_PRESSED)
144 enterPressed();
146 else
147 Screen<WindowType>::mouseButtonPressed(me);
151 /***********************************************************************/
153 bool Playlist::allowsFiltering()
155 return true;
158 std::string Playlist::currentFilter()
160 return RegexFilter<MPD::Song>::currentFilter(w);
163 void Playlist::applyFilter(const std::string &filter)
165 if (filter.empty())
167 w.clearFilter();
168 w.clearFilterResults();
169 return;
173 w.showAll();
174 auto rx = RegexFilter<MPD::Song>(
175 boost::regex(filter, Config.regex_type), playlistEntryMatcher);
176 w.filter(w.begin(), w.end(), rx);
178 catch (boost::bad_expression &) { }
181 /***********************************************************************/
183 bool Playlist::allowsSearching()
185 return true;
188 bool Playlist::search(const std::string &constraint)
190 if (constraint.empty())
192 w.clearSearchResults();
193 return false;
197 auto rx = RegexFilter<MPD::Song>(
198 boost::regex(constraint, Config.regex_type), playlistEntryMatcher);
199 return w.search(w.begin(), w.end(), rx);
201 catch (boost::bad_expression &)
203 return false;
207 void Playlist::nextFound(bool wrap)
209 w.nextFound(wrap);
212 void Playlist::prevFound(bool wrap)
214 w.prevFound(wrap);
217 /***********************************************************************/
219 ProxySongList Playlist::proxySongList()
221 return ProxySongList(w, [](NC::Menu<MPD::Song>::Item &item) {
222 return &item.value();
226 bool Playlist::allowsSelection()
228 return true;
231 void Playlist::reverseSelection()
233 reverseSelectionHelper(w.begin(), w.end());
236 MPD::SongList Playlist::getSelectedSongs()
238 MPD::SongList result;
239 for (auto it = w.begin(); it != w.end(); ++it)
240 if (it->isSelected())
241 result.push_back(it->value());
242 if (result.empty() && !w.empty())
243 result.push_back(w.current().value());
244 return result;
247 /***********************************************************************/
249 MPD::Song Playlist::nowPlayingSong()
251 MPD::Song s;
252 if (Status::get().playerState() != MPD::psStop)
253 withUnfilteredMenu(w, [this, &s]() {
254 s = w.at(Status::get().currentSongPosition()).value();
256 return s;
259 bool Playlist::isFiltered()
261 if (w.isFiltered())
263 Statusbar::print("Function currently unavailable due to filtered playlist");
264 return true;
266 return false;
269 void Playlist::Reverse()
271 Statusbar::print("Reversing playlist order...");
272 auto begin = w.begin(), end = w.end();
273 std::tie(begin, end) = getSelectedRange(begin, end);
274 Mpd.StartCommandsList();
275 for (--end; begin < end; ++begin, --end)
276 Mpd.Swap(begin->value().getPosition(), end->value().getPosition());
277 Mpd.CommitCommandsList();
278 Statusbar::print("Playlist reversed");
281 void Playlist::EnableHighlighting()
283 w.setHighlighting(true);
284 m_timer = Global::Timer;
287 std::string Playlist::getTotalLength()
289 std::ostringstream result;
291 if (m_reload_total_length)
293 m_total_length = 0;
294 for (const auto &s : w)
295 m_total_length += s.value().getDuration();
296 m_reload_total_length = false;
298 if (Config.playlist_show_remaining_time && m_reload_remaining && !w.isFiltered())
300 m_remaining_time = 0;
301 for (size_t i = Status::get().currentSongPosition(); i < w.size(); ++i)
302 m_remaining_time += w[i].value().getDuration();
303 m_reload_remaining = false;
306 result << '(' << w.size() << (w.size() == 1 ? " item" : " items");
308 if (w.isFiltered())
310 w.showAll();
311 size_t real_size = w.size();
312 w.showFiltered();
313 if (w.size() != real_size)
314 result << " (out of " << real_size << ")";
317 if (m_total_length)
319 result << ", length: ";
320 ShowTime(result, m_total_length, Config.playlist_shorten_total_times);
322 if (Config.playlist_show_remaining_time && m_remaining_time && !w.isFiltered() && w.size() > 1)
324 result << " :: remaining: ";
325 ShowTime(result, m_remaining_time, Config.playlist_shorten_total_times);
327 result << ')';
328 return result.str();
331 void Playlist::SetSelectedItemsPriority(int prio)
333 auto list = getSelectedOrCurrent(w.begin(), w.end(), w.currentI());
334 Mpd.StartCommandsList();
335 for (auto it = list.begin(); it != list.end(); ++it)
336 Mpd.SetPriority((*it)->value(), prio);
337 Mpd.CommitCommandsList();
338 Statusbar::print("Priority set");
341 bool Playlist::checkForSong(const MPD::Song &s)
343 return m_song_refs.find(s) != m_song_refs.end();
346 void Playlist::registerSong(const MPD::Song &s)
348 ++m_song_refs[s];
351 void Playlist::unregisterSong(const MPD::Song &s)
353 auto it = m_song_refs.find(s);
354 assert(it != m_song_refs.end());
355 if (it->second == 1)
356 m_song_refs.erase(it);
357 else
358 --it->second;
361 namespace {//
363 std::string songToString(const MPD::Song &s)
365 std::string result;
366 switch (Config.playlist_display_mode)
368 case DisplayMode::Classic:
369 result = s.toString(Config.song_list_format_dollar_free, Config.tags_separator);
370 break;
371 case DisplayMode::Columns:
372 result = s.toString(Config.song_in_columns_to_string_format, Config.tags_separator);
374 return result;
377 bool playlistEntryMatcher(const boost::regex &rx, const MPD::Song &s)
379 return boost::regex_search(songToString(s), rx);