Move screens to subdirectory
This commit is contained in:
761
src/screens/browser.cpp
Normal file
761
src/screens/browser.cpp
Normal file
@@ -0,0 +1,761 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/locale/conversion.hpp>
|
||||
#include <time.h>
|
||||
|
||||
#include "screens/browser.h"
|
||||
#include "charset.h"
|
||||
#include "display.h"
|
||||
#include "global.h"
|
||||
#include "helpers.h"
|
||||
#include "screens/playlist.h"
|
||||
#include "curses/menu_impl.h"
|
||||
#include "screens/screen_switcher.h"
|
||||
#include "settings.h"
|
||||
#include "status.h"
|
||||
#include "statusbar.h"
|
||||
#include "screens/tag_editor.h"
|
||||
#include "title.h"
|
||||
#include "tags.h"
|
||||
#include "helpers/song_iterator_maker.h"
|
||||
#include "utility/comparators.h"
|
||||
#include "utility/string.h"
|
||||
#include "configuration.h"
|
||||
|
||||
using Global::MainHeight;
|
||||
using Global::MainStartY;
|
||||
using Global::myScreen;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace ph = std::placeholders;
|
||||
|
||||
Browser *myBrowser;
|
||||
|
||||
namespace {
|
||||
|
||||
std::set<std::string> lm_supported_extensions;
|
||||
|
||||
std::string realPath(bool local_browser, std::string path);
|
||||
bool isStringParentDirectory(const std::string &directory);
|
||||
bool isItemParentDirectory(const MPD::Item &item);
|
||||
bool isRootDirectory(const std::string &directory);
|
||||
bool isHidden(const fs::directory_iterator &entry);
|
||||
bool hasSupportedExtension(const fs::directory_entry &entry);
|
||||
MPD::Song getLocalSong(const fs::directory_entry &entry, bool read_tags);
|
||||
void getLocalDirectory(std::vector<MPD::Item> &items, const std::string &directory);
|
||||
void getLocalDirectoryRecursively(std::vector<MPD::Song> &songs, const std::string &directory);
|
||||
void clearDirectory(const std::string &directory);
|
||||
|
||||
std::string itemToString(const MPD::Item &item);
|
||||
bool browserEntryMatcher(const Regex::Regex &rx, const MPD::Item &item, bool filter);
|
||||
|
||||
}
|
||||
|
||||
template <>
|
||||
struct SongPropertiesExtractor<MPD::Item>
|
||||
{
|
||||
template <typename ItemT>
|
||||
auto &operator()(ItemT &item) const
|
||||
{
|
||||
auto s = item.value().type() == MPD::Item::Type::Song
|
||||
? &item.value().song()
|
||||
: nullptr;
|
||||
m_cache.assign(&item.properties(), s);
|
||||
return m_cache;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable SongProperties m_cache;
|
||||
};
|
||||
|
||||
SongIterator BrowserWindow::currentS()
|
||||
{
|
||||
return makeSongIterator(current());
|
||||
}
|
||||
|
||||
ConstSongIterator BrowserWindow::currentS() const
|
||||
{
|
||||
return makeConstSongIterator(current());
|
||||
}
|
||||
|
||||
SongIterator BrowserWindow::beginS()
|
||||
{
|
||||
return makeSongIterator(begin());
|
||||
}
|
||||
|
||||
ConstSongIterator BrowserWindow::beginS() const
|
||||
{
|
||||
return makeConstSongIterator(begin());
|
||||
}
|
||||
|
||||
SongIterator BrowserWindow::endS()
|
||||
{
|
||||
return makeSongIterator(end());
|
||||
}
|
||||
|
||||
ConstSongIterator BrowserWindow::endS() const
|
||||
{
|
||||
return makeConstSongIterator(end());
|
||||
}
|
||||
|
||||
std::vector<MPD::Song> BrowserWindow::getSelectedSongs()
|
||||
{
|
||||
return {}; // TODO
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
Browser::Browser()
|
||||
: m_update_request(true)
|
||||
, m_local_browser(false)
|
||||
, m_scroll_beginning(0)
|
||||
, m_current_directory("/")
|
||||
{
|
||||
w = NC::Menu<MPD::Item>(0, MainStartY, COLS, MainHeight, Config.browser_display_mode == DisplayMode::Columns && Config.titles_visibility ? Display::Columns(COLS) : "", Config.main_color, NC::Border());
|
||||
w.setHighlightColor(Config.main_highlight_color);
|
||||
w.cyclicScrolling(Config.use_cyclic_scrolling);
|
||||
w.centeredCursor(Config.centered_cursor);
|
||||
w.setSelectedPrefix(Config.selected_item_prefix);
|
||||
w.setSelectedSuffix(Config.selected_item_suffix);
|
||||
w.setItemDisplayer(std::bind(Display::Items, ph::_1, std::cref(w)));
|
||||
}
|
||||
|
||||
void Browser::resize()
|
||||
{
|
||||
size_t x_offset, width;
|
||||
getWindowResizeParams(x_offset, width);
|
||||
w.resize(width, MainHeight);
|
||||
w.moveTo(x_offset, MainStartY);
|
||||
switch (Config.browser_display_mode)
|
||||
{
|
||||
case DisplayMode::Columns:
|
||||
if (Config.titles_visibility)
|
||||
{
|
||||
w.setTitle(Display::Columns(w.getWidth()));
|
||||
break;
|
||||
}
|
||||
case DisplayMode::Classic:
|
||||
w.setTitle("");
|
||||
break;
|
||||
}
|
||||
hasToBeResized = 0;
|
||||
}
|
||||
|
||||
void Browser::switchTo()
|
||||
{
|
||||
SwitchTo::execute(this);
|
||||
markSongsInPlaylist(w);
|
||||
drawHeader();
|
||||
}
|
||||
|
||||
std::wstring Browser::title()
|
||||
{
|
||||
std::wstring result = L"Browse: ";
|
||||
result += Scroller(ToWString(m_current_directory), m_scroll_beginning, COLS-result.length()-(Config.design == Design::Alternative ? 2 : Global::VolumeState.length()));
|
||||
return result;
|
||||
}
|
||||
|
||||
void Browser::update()
|
||||
{
|
||||
if (m_update_request)
|
||||
{
|
||||
m_update_request = false;
|
||||
bool directory_changed = false;
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
getDirectory(m_current_directory);
|
||||
w.refresh();
|
||||
}
|
||||
catch (MPD::ServerError &err)
|
||||
{
|
||||
// If current directory doesn't exist, try getting its
|
||||
// parent until we either succeed or reach the root.
|
||||
if (err.code() == MPD_SERVER_ERROR_NO_EXIST)
|
||||
{
|
||||
m_current_directory = getParentDirectory(m_current_directory);
|
||||
directory_changed = true;
|
||||
}
|
||||
else
|
||||
throw;
|
||||
}
|
||||
}
|
||||
while (w.empty() && !inRootDirectory());
|
||||
if (directory_changed)
|
||||
drawHeader();
|
||||
}
|
||||
}
|
||||
|
||||
void Browser::mouseButtonPressed(MEVENT me)
|
||||
{
|
||||
if (w.empty() || !w.hasCoords(me.x, me.y) || size_t(me.y) >= w.size())
|
||||
return;
|
||||
if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
|
||||
{
|
||||
w.Goto(me.y);
|
||||
switch (w.current()->value().type())
|
||||
{
|
||||
case MPD::Item::Type::Directory:
|
||||
if (me.bstate & BUTTON1_PRESSED)
|
||||
enterDirectory();
|
||||
else
|
||||
addItemToPlaylist(false);
|
||||
break;
|
||||
case MPD::Item::Type::Playlist:
|
||||
case MPD::Item::Type::Song:
|
||||
{
|
||||
bool play = me.bstate & BUTTON3_PRESSED;
|
||||
addItemToPlaylist(play);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
Screen<WindowType>::mouseButtonPressed(me);
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool Browser::allowsSearching()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string &Browser::searchConstraint()
|
||||
{
|
||||
return m_search_predicate.constraint();
|
||||
}
|
||||
|
||||
void Browser::setSearchConstraint(const std::string &constraint)
|
||||
{
|
||||
m_search_predicate = Regex::Filter<MPD::Item>(
|
||||
constraint,
|
||||
Config.regex_type,
|
||||
std::bind(browserEntryMatcher, ph::_1, ph::_2, false));
|
||||
}
|
||||
|
||||
void Browser::clearSearchConstraint()
|
||||
{
|
||||
m_search_predicate.clear();
|
||||
}
|
||||
|
||||
bool Browser::search(SearchDirection direction, bool wrap, bool skip_current)
|
||||
{
|
||||
return ::search(w, m_search_predicate, direction, wrap, skip_current);
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool Browser::allowsFiltering()
|
||||
{
|
||||
return allowsSearching();
|
||||
}
|
||||
|
||||
std::string Browser::currentFilter()
|
||||
{
|
||||
std::string result;
|
||||
if (auto pred = w.filterPredicate<Regex::Filter<MPD::Item>>())
|
||||
result = pred->constraint();
|
||||
return result;
|
||||
}
|
||||
|
||||
void Browser::applyFilter(const std::string &constraint)
|
||||
{
|
||||
if (!constraint.empty())
|
||||
{
|
||||
w.applyFilter(Regex::Filter<MPD::Item>(
|
||||
constraint,
|
||||
Config.regex_type,
|
||||
std::bind(browserEntryMatcher, ph::_1, ph::_2, true)));
|
||||
}
|
||||
else
|
||||
w.clearFilter();
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool Browser::itemAvailable()
|
||||
{
|
||||
return !w.empty()
|
||||
// ignore parent directory
|
||||
&& !isParentDirectory(w.current()->value());
|
||||
}
|
||||
|
||||
bool Browser::addItemToPlaylist(bool play)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
auto tryToPlay = [] {
|
||||
// Cheap trick that might fail in presence of multiple
|
||||
// clients modifying the playlist at the same time, but
|
||||
// oh well, this approach correctly loads cue playlists
|
||||
// and is much faster in general as it doesn't require
|
||||
// fetching song data.
|
||||
try
|
||||
{
|
||||
Mpd.Play(Status::State::playlistLength());
|
||||
}
|
||||
catch (MPD::ServerError &e)
|
||||
{
|
||||
// If not bad index, rethrow.
|
||||
if (e.code() != MPD_SERVER_ERROR_ARG)
|
||||
throw;
|
||||
}
|
||||
};
|
||||
|
||||
const MPD::Item &item = w.current()->value();
|
||||
switch (item.type())
|
||||
{
|
||||
case MPD::Item::Type::Directory:
|
||||
{
|
||||
if (m_local_browser)
|
||||
{
|
||||
std::vector<MPD::Song> songs;
|
||||
getLocalDirectoryRecursively(songs, item.directory().path());
|
||||
result = addSongsToPlaylist(songs.begin(), songs.end(), play, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Mpd.Add(item.directory().path());
|
||||
if (play)
|
||||
tryToPlay();
|
||||
result = true;
|
||||
}
|
||||
Statusbar::printf("Directory \"%1%\" added%2%",
|
||||
item.directory().path(), withErrors(result));
|
||||
break;
|
||||
}
|
||||
case MPD::Item::Type::Song:
|
||||
result = addSongToPlaylist(item.song(), play);
|
||||
break;
|
||||
case MPD::Item::Type::Playlist:
|
||||
Mpd.LoadPlaylist(item.playlist().path());
|
||||
if (play)
|
||||
tryToPlay();
|
||||
Statusbar::printf("Playlist \"%1%\" loaded", item.playlist().path());
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<MPD::Song> Browser::getSelectedSongs()
|
||||
{
|
||||
std::vector<MPD::Song> songs;
|
||||
auto item_handler = [this, &songs](const MPD::Item &item) {
|
||||
switch (item.type())
|
||||
{
|
||||
case MPD::Item::Type::Directory:
|
||||
if (m_local_browser)
|
||||
getLocalDirectoryRecursively(songs, item.directory().path());
|
||||
else
|
||||
{
|
||||
std::copy(
|
||||
std::make_move_iterator(Mpd.GetDirectoryRecursive(item.directory().path())),
|
||||
std::make_move_iterator(MPD::SongIterator()),
|
||||
std::back_inserter(songs)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case MPD::Item::Type::Song:
|
||||
songs.push_back(item.song());
|
||||
break;
|
||||
case MPD::Item::Type::Playlist:
|
||||
std::copy(
|
||||
std::make_move_iterator(Mpd.GetPlaylistContent(item.playlist().path())),
|
||||
std::make_move_iterator(MPD::SongIterator()),
|
||||
std::back_inserter(songs)
|
||||
);
|
||||
break;
|
||||
}
|
||||
};
|
||||
for (const auto &item : w)
|
||||
if (item.isSelected())
|
||||
item_handler(item.value());
|
||||
// if no item is selected, add current one
|
||||
if (songs.empty() && !w.empty())
|
||||
item_handler(w.current()->value());
|
||||
return songs;
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool Browser::inRootDirectory()
|
||||
{
|
||||
return isRootDirectory(m_current_directory);
|
||||
}
|
||||
|
||||
bool Browser::isParentDirectory(const MPD::Item &item)
|
||||
{
|
||||
return isItemParentDirectory(item);
|
||||
}
|
||||
|
||||
const std::string& Browser::currentDirectory()
|
||||
{
|
||||
return m_current_directory;
|
||||
}
|
||||
|
||||
void Browser::locateSong(const MPD::Song &s)
|
||||
{
|
||||
if (s.getDirectory().empty())
|
||||
throw std::runtime_error("Song's directory is empty");
|
||||
|
||||
m_local_browser = !s.isFromDatabase();
|
||||
|
||||
if (myScreen != this)
|
||||
switchTo();
|
||||
|
||||
w.clearFilter();
|
||||
|
||||
// change to relevant directory
|
||||
if (m_current_directory != s.getDirectory())
|
||||
{
|
||||
getDirectory(s.getDirectory());
|
||||
drawHeader();
|
||||
}
|
||||
|
||||
// highlight the item
|
||||
auto begin = w.beginV(), end = w.endV();
|
||||
auto it = std::find(begin, end, MPD::Item(s));
|
||||
if (it != end)
|
||||
w.highlight(it-begin);
|
||||
}
|
||||
|
||||
bool Browser::enterDirectory()
|
||||
{
|
||||
bool result = false;
|
||||
if (!w.empty())
|
||||
{
|
||||
const auto &item = w.current()->value();
|
||||
if (item.type() == MPD::Item::Type::Directory)
|
||||
{
|
||||
getDirectory(item.directory().path());
|
||||
drawHeader();
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Browser::getDirectory(std::string directory)
|
||||
{
|
||||
ScopedUnfilteredMenu<MPD::Item> sunfilter(ReapplyFilter::Yes, w);
|
||||
|
||||
m_scroll_beginning = 0;
|
||||
w.clear();
|
||||
|
||||
// reset the position if we change directories
|
||||
if (m_current_directory != directory)
|
||||
w.reset();
|
||||
|
||||
// check if it's a parent directory
|
||||
if (isStringParentDirectory(directory))
|
||||
{
|
||||
directory.resize(directory.length()-3);
|
||||
directory = getParentDirectory(directory);
|
||||
}
|
||||
// when we go down to root, it can be empty
|
||||
if (directory.empty())
|
||||
directory = "/";
|
||||
|
||||
std::vector<MPD::Item> items;
|
||||
if (m_local_browser)
|
||||
getLocalDirectory(items, directory);
|
||||
else
|
||||
{
|
||||
std::copy(
|
||||
std::make_move_iterator(Mpd.GetDirectory(directory)),
|
||||
std::make_move_iterator(MPD::ItemIterator()),
|
||||
std::back_inserter(items)
|
||||
);
|
||||
}
|
||||
|
||||
// sort items
|
||||
if (Config.browser_sort_mode != SortMode::NoOp)
|
||||
{
|
||||
std::sort(items.begin(), items.end(),
|
||||
LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode)
|
||||
);
|
||||
}
|
||||
|
||||
// if the requested directory is not root, add parent directory
|
||||
if (!isRootDirectory(directory))
|
||||
{
|
||||
// make it so that display function doesn't have to handle special cases
|
||||
w.addItem(MPD::Directory(directory + "/.."), NC::List::Properties::None);
|
||||
}
|
||||
|
||||
for (const auto &item : items)
|
||||
{
|
||||
switch (item.type())
|
||||
{
|
||||
case MPD::Item::Type::Playlist:
|
||||
{
|
||||
w.addItem(std::move(item));
|
||||
break;
|
||||
}
|
||||
case MPD::Item::Type::Directory:
|
||||
{
|
||||
bool is_current = item.directory().path() == m_current_directory;
|
||||
w.addItem(std::move(item));
|
||||
if (is_current)
|
||||
w.highlight(w.size()-1);
|
||||
break;
|
||||
}
|
||||
case MPD::Item::Type::Song:
|
||||
{
|
||||
auto properties = NC::List::Properties::Selectable;
|
||||
if (myPlaylist->checkForSong(item.song()))
|
||||
properties |= NC::List::Properties::Bold;
|
||||
w.addItem(std::move(item), properties);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_current_directory = directory;
|
||||
}
|
||||
|
||||
void Browser::changeBrowseMode()
|
||||
{
|
||||
if (Mpd.GetHostname()[0] != '/')
|
||||
{
|
||||
Statusbar::print("For browsing local filesystem connection to MPD via UNIX Socket is required");
|
||||
return;
|
||||
}
|
||||
|
||||
m_local_browser = !m_local_browser;
|
||||
Statusbar::printf("Browse mode: %1%",
|
||||
m_local_browser ? "local filesystem" : "MPD database"
|
||||
);
|
||||
if (m_local_browser)
|
||||
{
|
||||
m_current_directory = "~";
|
||||
expand_home(m_current_directory);
|
||||
}
|
||||
else
|
||||
m_current_directory = "/";
|
||||
w.reset();
|
||||
getDirectory(m_current_directory);
|
||||
drawHeader();
|
||||
}
|
||||
|
||||
void Browser::remove(const MPD::Item &item)
|
||||
{
|
||||
if (!Config.allow_for_physical_item_deletion)
|
||||
throw std::runtime_error("physical deletion is forbidden");
|
||||
if (isParentDirectory((item)))
|
||||
throw std::runtime_error("deletion of parent directory is forbidden");
|
||||
|
||||
std::string path;
|
||||
switch (item.type())
|
||||
{
|
||||
case MPD::Item::Type::Directory:
|
||||
path = realPath(m_local_browser, item.directory().path());
|
||||
clearDirectory(path);
|
||||
fs::remove(path);
|
||||
break;
|
||||
case MPD::Item::Type::Song:
|
||||
path = realPath(m_local_browser, item.song().getURI());
|
||||
fs::remove(path);
|
||||
break;
|
||||
case MPD::Item::Type::Playlist:
|
||||
path = item.playlist().path();
|
||||
try {
|
||||
Mpd.DeletePlaylist(path);
|
||||
} catch (MPD::ServerError &e) {
|
||||
// if there is no such mpd playlist, it's a local one
|
||||
if (e.code() == MPD_SERVER_ERROR_NO_EXIST)
|
||||
{
|
||||
path = realPath(m_local_browser, std::move(path));
|
||||
fs::remove(path);
|
||||
}
|
||||
else
|
||||
throw;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
void Browser::fetchSupportedExtensions()
|
||||
{
|
||||
lm_supported_extensions.clear();
|
||||
MPD::StringIterator extension = Mpd.GetSupportedExtensions(), end;
|
||||
for (; extension != end; ++extension)
|
||||
lm_supported_extensions.insert("." + std::move(*extension));
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
namespace {
|
||||
|
||||
std::string realPath(bool local_browser, std::string path)
|
||||
{
|
||||
if (!local_browser)
|
||||
path = Config.mpd_music_dir + path;
|
||||
return path;
|
||||
}
|
||||
|
||||
bool isStringParentDirectory(const std::string &directory)
|
||||
{
|
||||
return boost::algorithm::ends_with(directory, "/..");
|
||||
}
|
||||
|
||||
bool isItemParentDirectory(const MPD::Item &item)
|
||||
{
|
||||
return item.type() == MPD::Item::Type::Directory
|
||||
&& isStringParentDirectory(item.directory().path());
|
||||
}
|
||||
|
||||
bool isRootDirectory(const std::string &directory)
|
||||
{
|
||||
return directory == "/";
|
||||
}
|
||||
|
||||
bool isHidden(const fs::directory_iterator &entry)
|
||||
{
|
||||
return entry->path().filename().native()[0] == '.';
|
||||
}
|
||||
|
||||
bool hasSupportedExtension(const fs::directory_entry &entry)
|
||||
{
|
||||
return lm_supported_extensions.find(entry.path().extension().native())
|
||||
!= lm_supported_extensions.end();
|
||||
}
|
||||
|
||||
MPD::Song getLocalSong(const fs::directory_entry &entry, bool read_tags)
|
||||
{
|
||||
mpd_pair pair = { "file", entry.path().c_str() };
|
||||
mpd_song *s = mpd_song_begin(&pair);
|
||||
if (s == nullptr)
|
||||
throw std::runtime_error("invalid path: " + entry.path().native());
|
||||
if (read_tags)
|
||||
{
|
||||
#ifdef HAVE_TAGLIB_H
|
||||
Tags::setAttribute(s, "Last-Modified",
|
||||
timeFormat("%Y-%m-%dT%H:%M:%SZ", fs::last_write_time(entry.path()))
|
||||
);
|
||||
// read tags
|
||||
Tags::read(s);
|
||||
#endif // HAVE_TAGLIB_H
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void getLocalDirectory(std::vector<MPD::Item> &items, const std::string &directory)
|
||||
{
|
||||
for (fs::directory_iterator entry(directory), end; entry != end; ++entry)
|
||||
{
|
||||
if (!Config.local_browser_show_hidden_files && isHidden(entry))
|
||||
continue;
|
||||
|
||||
if (fs::is_directory(*entry))
|
||||
{
|
||||
items.push_back(MPD::Directory(
|
||||
entry->path().native(),
|
||||
fs::last_write_time(entry->path())
|
||||
));
|
||||
}
|
||||
else if (hasSupportedExtension(*entry))
|
||||
items.push_back(getLocalSong(*entry, true));
|
||||
}
|
||||
}
|
||||
|
||||
void getLocalDirectoryRecursively(std::vector<MPD::Song> &songs, const std::string &directory)
|
||||
{
|
||||
size_t sort_offset = songs.size();
|
||||
for (fs::directory_iterator entry(directory), end; entry != end; ++entry)
|
||||
{
|
||||
if (!Config.local_browser_show_hidden_files && isHidden(entry))
|
||||
continue;
|
||||
|
||||
if (fs::is_directory(*entry))
|
||||
{
|
||||
getLocalDirectoryRecursively(songs, entry->path().native());
|
||||
sort_offset = songs.size();
|
||||
}
|
||||
else if (hasSupportedExtension(*entry))
|
||||
songs.push_back(getLocalSong(*entry, false));
|
||||
};
|
||||
|
||||
if (Config.browser_sort_mode != SortMode::NoOp)
|
||||
{
|
||||
std::sort(songs.begin()+sort_offset, songs.end(),
|
||||
LocaleBasedSorting(std::locale(), Config.ignore_leading_the)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void clearDirectory(const std::string &directory)
|
||||
{
|
||||
for (fs::directory_iterator entry(directory), end; entry != end; ++entry)
|
||||
{
|
||||
if (!fs::is_symlink(*entry) && fs::is_directory(*entry))
|
||||
clearDirectory(entry->path().native());
|
||||
const char msg[] = "Deleting \"%1%\"...";
|
||||
Statusbar::printf(msg, wideShorten(entry->path().native(), COLS-const_strlen(msg)));
|
||||
fs::remove(entry->path());
|
||||
};
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
std::string itemToString(const MPD::Item &item)
|
||||
{
|
||||
std::string result;
|
||||
switch (item.type())
|
||||
{
|
||||
case MPD::Item::Type::Directory:
|
||||
result = "[" + getBasename(item.directory().path()) + "]";
|
||||
break;
|
||||
case MPD::Item::Type::Song:
|
||||
switch (Config.browser_display_mode)
|
||||
{
|
||||
case DisplayMode::Classic:
|
||||
result = Format::stringify<char>(Config.song_list_format, &item.song());
|
||||
break;
|
||||
case DisplayMode::Columns:
|
||||
result = Format::stringify<char>(Config.song_columns_mode_format, &item.song());
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MPD::Item::Type::Playlist:
|
||||
result = Config.browser_playlist_prefix.str();
|
||||
result += getBasename(item.playlist().path());
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool browserEntryMatcher(const Regex::Regex &rx, const MPD::Item &item, bool filter)
|
||||
{
|
||||
if (isItemParentDirectory(item))
|
||||
return filter;
|
||||
return Regex::search(itemToString(item), rx);
|
||||
}
|
||||
|
||||
}
|
||||
108
src/screens/browser.h
Normal file
108
src/screens/browser.h
Normal file
@@ -0,0 +1,108 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_BROWSER_H
|
||||
#define NCMPCPP_BROWSER_H
|
||||
|
||||
#include "interfaces.h"
|
||||
#include "mpdpp.h"
|
||||
#include "regex_filter.h"
|
||||
#include "screens/screen.h"
|
||||
#include "song_list.h"
|
||||
|
||||
struct BrowserWindow: NC::Menu<MPD::Item>, SongList
|
||||
{
|
||||
BrowserWindow() { }
|
||||
BrowserWindow(NC::Menu<MPD::Item> &&base)
|
||||
: NC::Menu<MPD::Item>(std::move(base)) { }
|
||||
|
||||
virtual SongIterator currentS() override;
|
||||
virtual ConstSongIterator currentS() const override;
|
||||
virtual SongIterator beginS() override;
|
||||
virtual ConstSongIterator beginS() const override;
|
||||
virtual SongIterator endS() override;
|
||||
virtual ConstSongIterator endS() const override;
|
||||
|
||||
virtual std::vector<MPD::Song> getSelectedSongs() override;
|
||||
};
|
||||
|
||||
struct Browser: Screen<BrowserWindow>, Filterable, HasSongs, Searchable, Tabbable
|
||||
{
|
||||
Browser();
|
||||
|
||||
// Screen<BrowserWindow> implementation
|
||||
virtual void resize() override;
|
||||
virtual void switchTo() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::Browser; }
|
||||
|
||||
virtual void update() override;
|
||||
|
||||
virtual void mouseButtonPressed(MEVENT me) override;
|
||||
|
||||
virtual bool isLockable() override { return true; }
|
||||
virtual bool isMergable() override { return true; }
|
||||
|
||||
// Searchable implementation
|
||||
virtual bool allowsSearching() override;
|
||||
virtual const std::string &searchConstraint() override;
|
||||
virtual void setSearchConstraint(const std::string &constraint) override;
|
||||
virtual void clearSearchConstraint() override;
|
||||
virtual bool search(SearchDirection direction, bool wrap, bool skip_current) override;
|
||||
|
||||
// Filterable implemenetation
|
||||
virtual bool allowsFiltering() 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
|
||||
void requestUpdate() { m_update_request = true; }
|
||||
|
||||
bool inRootDirectory();
|
||||
bool isParentDirectory(const MPD::Item &item);
|
||||
const std::string ¤tDirectory();
|
||||
|
||||
bool isLocal() { return m_local_browser; }
|
||||
void locateSong(const MPD::Song &s);
|
||||
bool enterDirectory();
|
||||
void getDirectory(std::string directory);
|
||||
void changeBrowseMode();
|
||||
void remove(const MPD::Item &item);
|
||||
|
||||
static void fetchSupportedExtensions();
|
||||
|
||||
private:
|
||||
bool m_update_request;
|
||||
bool m_local_browser;
|
||||
size_t m_scroll_beginning;
|
||||
std::string m_current_directory;
|
||||
Regex::Filter<MPD::Item> m_search_predicate;
|
||||
};
|
||||
|
||||
extern Browser *myBrowser;
|
||||
|
||||
#endif // NCMPCPP_BROWSER_H
|
||||
|
||||
192
src/screens/clock.cpp
Normal file
192
src/screens/clock.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
/// NOTICE: Major part of this code is ported from ncmpc's clock screen
|
||||
|
||||
#include "screens/clock.h"
|
||||
|
||||
#ifdef ENABLE_CLOCK
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <cstring>
|
||||
|
||||
#include "global.h"
|
||||
#include "screens/playlist.h"
|
||||
#include "settings.h"
|
||||
#include "status.h"
|
||||
#include "statusbar.h"
|
||||
#include "title.h"
|
||||
#include "screens/screen_switcher.h"
|
||||
|
||||
using Global::MainHeight;
|
||||
using Global::MainStartY;
|
||||
using Global::myScreen;
|
||||
|
||||
Clock *myClock;
|
||||
|
||||
short Clock::disp[11] =
|
||||
{
|
||||
075557, 011111, 071747, 071717,
|
||||
055711, 074717, 074757, 071111,
|
||||
075757, 075717, 002020
|
||||
};
|
||||
|
||||
long Clock::older[6], Clock::next[6], Clock::newer[6], Clock::mask;
|
||||
|
||||
size_t Clock::Width;
|
||||
const size_t Clock::Height = 8;
|
||||
|
||||
Clock::Clock()
|
||||
{
|
||||
Width = Config.clock_display_seconds ? 60 : 40;
|
||||
|
||||
m_pane = NC::Window(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border());
|
||||
w = NC::Window((COLS-Width)/2, (MainHeight-Height)/2+MainStartY, Width, Height-1, "", Config.main_color, Config.main_color);
|
||||
}
|
||||
|
||||
void Clock::resize()
|
||||
{
|
||||
size_t x_offset, width;
|
||||
getWindowResizeParams(x_offset, width);
|
||||
|
||||
// used for clearing area out of clock window while resizing terminal
|
||||
m_pane.resize(width, MainHeight);
|
||||
m_pane.moveTo(x_offset, MainStartY);
|
||||
m_pane.refresh();
|
||||
|
||||
if (Width <= width && Height <= MainHeight)
|
||||
w.moveTo(x_offset+(width-Width)/2, MainStartY+(MainHeight-Height)/2);
|
||||
}
|
||||
|
||||
void Clock::switchTo()
|
||||
{
|
||||
size_t x_offset, width;
|
||||
getWindowResizeParams(x_offset, width, false);
|
||||
if (Width > width || Height > MainHeight)
|
||||
Statusbar::print("Screen is too small to display clock");
|
||||
else
|
||||
{
|
||||
SwitchTo::execute(this);
|
||||
drawHeader();
|
||||
Prepare();
|
||||
m_pane.refresh();
|
||||
// clearing screen apparently fixes the problem with last digits being misrendered
|
||||
w.clear();
|
||||
w.display();
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring Clock::title()
|
||||
{
|
||||
return L"Clock";
|
||||
}
|
||||
|
||||
void Clock::update()
|
||||
{
|
||||
if (Width > m_pane.getWidth() || Height > MainHeight)
|
||||
{
|
||||
using Global::myLockedScreen;
|
||||
using Global::myInactiveScreen;
|
||||
|
||||
if (myLockedScreen)
|
||||
{
|
||||
if (myInactiveScreen != myLockedScreen)
|
||||
myScreen = myInactiveScreen;
|
||||
myLockedScreen->switchTo();
|
||||
}
|
||||
else
|
||||
myPlaylist->switchTo();
|
||||
}
|
||||
|
||||
auto time = boost::posix_time::to_tm(Global::Timer);
|
||||
|
||||
mask = 0;
|
||||
Set(time.tm_sec % 10, 0);
|
||||
Set(time.tm_sec / 10, 4);
|
||||
Set(time.tm_min % 10, 10);
|
||||
Set(time.tm_min / 10, 14);
|
||||
Set(time.tm_hour % 10, 20);
|
||||
Set(time.tm_hour / 10, 24);
|
||||
Set(10, 7);
|
||||
Set(10, 17);
|
||||
|
||||
char buf[64];
|
||||
std::strftime(buf, 64, "%x", &time);
|
||||
color_set(Config.main_color.pairNumber(), nullptr);
|
||||
mvprintw(w.getStarty()+w.getHeight(), w.getStartX()+(w.getWidth()-strlen(buf))/2, "%s", buf);
|
||||
standend();
|
||||
refresh();
|
||||
|
||||
for (int k = 0; k < 6; ++k)
|
||||
{
|
||||
newer[k] = (newer[k] & ~mask) | (next[k] & mask);
|
||||
next[k] = 0;
|
||||
for (int s = 1; s >= 0; --s)
|
||||
{
|
||||
w << (s ? NC::Format::Reverse : NC::Format::NoReverse);
|
||||
for (int i = 0; i < 6; ++i)
|
||||
{
|
||||
long a = (newer[i] ^ older[i]) & (s ? newer : older)[i];
|
||||
if (a != 0)
|
||||
{
|
||||
long t = 1 << 26;
|
||||
for (int j = 0; t; t >>= 1, ++j)
|
||||
{
|
||||
if (a & t)
|
||||
{
|
||||
if (!(a & (t << 1)))
|
||||
{
|
||||
w.goToXY(2*j+2, i);
|
||||
}
|
||||
if (Config.clock_display_seconds || j < 18)
|
||||
w << " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!s)
|
||||
{
|
||||
older[i] = newer[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
w.refresh();
|
||||
}
|
||||
|
||||
void Clock::Prepare()
|
||||
{
|
||||
for (int i = 0; i < 5; ++i)
|
||||
older[i] = newer[i] = next[i] = 0;
|
||||
}
|
||||
|
||||
void Clock::Set(int t, int n)
|
||||
{
|
||||
int m = 7 << n;
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
next[i] |= ((disp[t] >> ((4 - i) * 3)) & 07) << n;
|
||||
mask |= (next[i] ^ older[i]) & m;
|
||||
}
|
||||
if (mask & m)
|
||||
mask |= m;
|
||||
}
|
||||
|
||||
#endif // ENABLE_CLOCK
|
||||
|
||||
67
src/screens/clock.h
Normal file
67
src/screens/clock.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_CLOCK_H
|
||||
#define NCMPCPP_CLOCK_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef ENABLE_CLOCK
|
||||
|
||||
#include "curses/window.h"
|
||||
#include "interfaces.h"
|
||||
#include "screens/screen.h"
|
||||
|
||||
struct Clock: Screen<NC::Window>, Tabbable
|
||||
{
|
||||
Clock();
|
||||
|
||||
virtual void resize() override;
|
||||
virtual void switchTo() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::Clock; }
|
||||
|
||||
virtual void update() override;
|
||||
virtual void scroll(NC::Scroll) override { }
|
||||
|
||||
virtual void mouseButtonPressed(MEVENT) override { }
|
||||
|
||||
virtual bool isLockable() override { return false; }
|
||||
virtual bool isMergable() override { return true; }
|
||||
|
||||
private:
|
||||
NC::Window m_pane;
|
||||
|
||||
static void Prepare();
|
||||
static void Set(int, int);
|
||||
|
||||
static short disp[11];
|
||||
static long older[6], next[6], newer[6], mask;
|
||||
|
||||
static size_t Width;
|
||||
static const size_t Height;
|
||||
};
|
||||
|
||||
extern Clock *myClock;
|
||||
|
||||
#endif // ENABLE_CLOCK
|
||||
|
||||
#endif // NCMPCPP_CLOCK_H
|
||||
456
src/screens/help.cpp
Normal file
456
src/screens/help.cpp
Normal file
@@ -0,0 +1,456 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "mpdpp.h"
|
||||
|
||||
#include "bindings.h"
|
||||
#include "global.h"
|
||||
#include "screens/help.h"
|
||||
#include "settings.h"
|
||||
#include "status.h"
|
||||
#include "utility/string.h"
|
||||
#include "utility/wide_string.h"
|
||||
#include "title.h"
|
||||
#include "screens/screen_switcher.h"
|
||||
|
||||
using Global::MainHeight;
|
||||
using Global::MainStartY;
|
||||
|
||||
Help *myHelp;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string align_key_rep(std::wstring keys)
|
||||
{
|
||||
size_t i = 0, len = 0;
|
||||
const size_t max_len = 20;
|
||||
for (; i < keys.size(); ++i)
|
||||
{
|
||||
int width = std::max(1, wcwidth(keys[i]));
|
||||
if (len+width > max_len)
|
||||
break;
|
||||
else
|
||||
len += width;
|
||||
}
|
||||
keys.resize(i + max_len - len, ' ');
|
||||
return ToString(keys);
|
||||
}
|
||||
|
||||
std::string display_keys(const Actions::Type at)
|
||||
{
|
||||
std::wstring result, skey;
|
||||
for (auto it = Bindings.begin(); it != Bindings.end(); ++it)
|
||||
{
|
||||
for (auto j = it->second.begin(); j != it->second.end(); ++j)
|
||||
{
|
||||
if (j->isSingle() && j->action().type() == at)
|
||||
{
|
||||
skey = keyToWString(it->first);
|
||||
if (!skey.empty())
|
||||
{
|
||||
result += std::move(skey);
|
||||
result += ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return align_key_rep(std::move(result));
|
||||
}
|
||||
|
||||
void section(NC::Scrollpad &w, const char *type_, const char *title_)
|
||||
{
|
||||
w << "\n " << NC::Format::Bold;
|
||||
if (type_[0] != '\0')
|
||||
w << type_ << " - ";
|
||||
w << title_ << NC::Format::NoBold << "\n\n";
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
void key_section(NC::Scrollpad &w, const char *title_)
|
||||
{
|
||||
section(w, "Keys", title_);
|
||||
}
|
||||
|
||||
void key(NC::Scrollpad &w, const Actions::Type at, const char *desc)
|
||||
{
|
||||
w << " " << display_keys(at) << " : " << desc << '\n';
|
||||
}
|
||||
|
||||
void key(NC::Scrollpad &w, const Actions::Type at, const boost::format &desc)
|
||||
{
|
||||
w << " " << display_keys(at) << " : " << desc.str() << '\n';
|
||||
}
|
||||
|
||||
void key(NC::Scrollpad &w, NC::Key::Type k, const std::string &desc)
|
||||
{
|
||||
w << " " << align_key_rep(keyToWString(k)) << " : " << desc << '\n';
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
void mouse_section(NC::Scrollpad &w, const char *title_)
|
||||
{
|
||||
section(w, "Mouse", title_);
|
||||
}
|
||||
|
||||
void mouse(NC::Scrollpad &w, std::string action, const char *desc, bool indent = false)
|
||||
{
|
||||
action.resize(31 - (indent ? 2 : 0), ' ');
|
||||
w << " " << (indent ? " " : "") << action;
|
||||
w << ": " << desc << '\n';
|
||||
}
|
||||
|
||||
void mouse_column(NC::Scrollpad &w, const char *column)
|
||||
{
|
||||
w << NC::Format::Bold << " " << column << " column:\n" << NC::Format::NoBold;
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
void write_bindings(NC::Scrollpad &w)
|
||||
{
|
||||
using Actions::Type;
|
||||
|
||||
key_section(w, "Movement");
|
||||
key(w, Type::ScrollUp, "Move cursor up");
|
||||
key(w, Type::ScrollDown, "Move cursor down");
|
||||
key(w, Type::ScrollUpAlbum, "Move cursor up one album");
|
||||
key(w, Type::ScrollDownAlbum, "Move cursor down one album");
|
||||
key(w, Type::ScrollUpArtist, "Move cursor up one artist");
|
||||
key(w, Type::ScrollDownArtist, "Move cursor down one artist");
|
||||
key(w, Type::PageUp, "Page up");
|
||||
key(w, Type::PageDown, "Page down");
|
||||
key(w, Type::MoveHome, "Home");
|
||||
key(w, Type::MoveEnd, "End");
|
||||
w << '\n';
|
||||
if (Config.screen_switcher_previous)
|
||||
{
|
||||
key(w, Type::NextScreen, "Switch between current and last screen");
|
||||
key(w, Type::PreviousScreen, "Switch between current and last screen");
|
||||
}
|
||||
else
|
||||
{
|
||||
key(w, Type::NextScreen, "Switch to next screen in sequence");
|
||||
key(w, Type::PreviousScreen, "Switch to previous screen in sequence");
|
||||
}
|
||||
key(w, Type::ShowHelp, "Show help");
|
||||
key(w, Type::ShowPlaylist, "Show playlist");
|
||||
key(w, Type::ShowBrowser, "Show browser");
|
||||
key(w, Type::ShowSearchEngine, "Show search engine");
|
||||
key(w, Type::ShowMediaLibrary, "Show media library");
|
||||
key(w, Type::ShowPlaylistEditor, "Show playlist editor");
|
||||
# ifdef HAVE_TAGLIB_H
|
||||
key(w, Type::ShowTagEditor, "Show tag editor");
|
||||
# endif // HAVE_TAGLIB_H
|
||||
# ifdef ENABLE_OUTPUTS
|
||||
key(w, Type::ShowOutputs, "Show outputs");
|
||||
# endif // ENABLE_OUTPUTS
|
||||
# ifdef ENABLE_VISUALIZER
|
||||
key(w, Type::ShowVisualizer, "Show music visualizer");
|
||||
# endif // ENABLE_VISUALIZER
|
||||
# ifdef ENABLE_CLOCK
|
||||
key(w, Type::ShowClock, "Show clock");
|
||||
# endif // ENABLE_CLOCK
|
||||
w << '\n';
|
||||
key(w, Type::ShowServerInfo, "Show server info");
|
||||
|
||||
key_section(w, "Global");
|
||||
key(w, Type::Stop, "Stop");
|
||||
key(w, Type::Pause, "Pause");
|
||||
key(w, Type::Next, "Next track");
|
||||
key(w, Type::Previous, "Previous track");
|
||||
key(w, Type::ReplaySong, "Replay playing song");
|
||||
key(w, Type::SeekForward, "Seek forward in playing song");
|
||||
key(w, Type::SeekBackward, "Seek backward in playing song");
|
||||
key(w, Type::VolumeDown,
|
||||
boost::format("Decrease volume by %1%%%") % Config.volume_change_step
|
||||
);
|
||||
key(w, Type::VolumeUp,
|
||||
boost::format("Increase volume by %1%%%") % Config.volume_change_step
|
||||
);
|
||||
w << '\n';
|
||||
key(w, Type::ToggleAddMode, "Toggle add mode (add or remove/always add)");
|
||||
key(w, Type::ToggleMouse, "Toggle mouse support");
|
||||
key(w, Type::SelectRange, "Select range");
|
||||
key(w, Type::ReverseSelection, "Reverse selection");
|
||||
key(w, Type::RemoveSelection, "Remove selection");
|
||||
key(w, Type::SelectItem, "Select current item");
|
||||
key(w, Type::SelectFoundItems, "Select found items");
|
||||
key(w, Type::SelectAlbum, "Select songs of album around the cursor");
|
||||
key(w, Type::AddSelectedItems, "Add selected items to playlist");
|
||||
key(w, Type::AddRandomItems, "Add random items to playlist");
|
||||
w << '\n';
|
||||
key(w, Type::ToggleRepeat, "Toggle repeat mode");
|
||||
key(w, Type::ToggleRandom, "Toggle random mode");
|
||||
key(w, Type::ToggleSingle, "Toggle single mode");
|
||||
key(w, Type::ToggleConsume, "Toggle consume mode");
|
||||
key(w, Type::ToggleReplayGainMode, "Toggle replay gain mode");
|
||||
key(w, Type::ToggleBitrateVisibility, "Toggle bitrate visibility");
|
||||
key(w, Type::ToggleCrossfade, "Toggle crossfade mode");
|
||||
key(w, Type::SetCrossfade, "Set crossfade");
|
||||
key(w, Type::SetVolume, "Set volume");
|
||||
key(w, Type::UpdateDatabase, "Start music database update");
|
||||
w << '\n';
|
||||
key(w, Type::ExecuteCommand, "Execute command");
|
||||
key(w, Type::ApplyFilter, "Apply filter");
|
||||
key(w, Type::FindItemForward, "Find item forward");
|
||||
key(w, Type::FindItemBackward, "Find item backward");
|
||||
key(w, Type::PreviousFoundItem, "Jump to previous found item");
|
||||
key(w, Type::NextFoundItem, "Jump to next found item");
|
||||
key(w, Type::ToggleFindMode, "Toggle find mode (normal/wrapped)");
|
||||
key(w, Type::JumpToBrowser, "Locate song in browser");
|
||||
key(w, Type::JumpToMediaLibrary, "Locate song in media library");
|
||||
key(w, Type::ToggleScreenLock, "Lock/unlock current screen");
|
||||
key(w, Type::MasterScreen, "Switch to master screen (left one)");
|
||||
key(w, Type::SlaveScreen, "Switch to slave screen (right one)");
|
||||
# ifdef HAVE_TAGLIB_H
|
||||
key(w, Type::JumpToTagEditor, "Locate song in tag editor");
|
||||
# endif // HAVE_TAGLIB_H
|
||||
key(w, Type::ToggleDisplayMode, "Toggle display mode");
|
||||
key(w, Type::ToggleInterface, "Toggle user interface");
|
||||
key(w, Type::ToggleSeparatorsBetweenAlbums, "Toggle displaying separators between albums");
|
||||
key(w, Type::JumpToPositionInSong, "Jump to given position in playing song (formats: mm:ss, x%)");
|
||||
key(w, Type::ShowSongInfo, "Show song info");
|
||||
key(w, Type::ShowArtistInfo, "Show artist info");
|
||||
key(w, Type::FetchLyricsInBackground, "Fetch lyrics for selected songs");
|
||||
key(w, Type::ToggleLyricsFetcher, "Toggle lyrics fetcher");
|
||||
key(w, Type::ToggleFetchingLyricsInBackground, "Toggle fetching lyrics for playing songs in background");
|
||||
key(w, Type::ShowLyrics, "Show/hide song lyrics");
|
||||
w << '\n';
|
||||
key(w, Type::Quit, "Quit");
|
||||
|
||||
key_section(w, "Playlist");
|
||||
key(w, Type::PlayItem, "Play selected item");
|
||||
key(w, Type::DeletePlaylistItems, "Delete selected item(s) from playlist");
|
||||
key(w, Type::ClearMainPlaylist, "Clear playlist");
|
||||
key(w, Type::CropMainPlaylist, "Clear playlist except selected item(s)");
|
||||
key(w, Type::SetSelectedItemsPriority, "Set priority of selected items");
|
||||
key(w, Type::MoveSelectedItemsUp, "Move selected item(s) up");
|
||||
key(w, Type::MoveSelectedItemsDown, "Move selected item(s) down");
|
||||
key(w, Type::MoveSelectedItemsTo, "Move selected item(s) to cursor position");
|
||||
key(w, Type::Add, "Add item to playlist");
|
||||
# ifdef HAVE_TAGLIB_H
|
||||
key(w, Type::EditSong, "Edit song");
|
||||
# endif // HAVE_TAGLIB_H
|
||||
key(w, Type::SavePlaylist, "Save playlist");
|
||||
key(w, Type::Shuffle, "Shuffle range");
|
||||
key(w, Type::SortPlaylist, "Sort range");
|
||||
key(w, Type::ReversePlaylist, "Reverse range");
|
||||
key(w, Type::JumpToPlayingSong, "Jump to current song");
|
||||
key(w, Type::TogglePlayingSongCentering, "Toggle playing song centering");
|
||||
|
||||
key_section(w, "Browser");
|
||||
key(w, Type::EnterDirectory, "Enter directory");
|
||||
key(w, Type::PlayItem, "Add item to playlist and play it");
|
||||
key(w, Type::AddItemToPlaylist, "Add item to playlist");
|
||||
# ifdef HAVE_TAGLIB_H
|
||||
key(w, Type::EditSong, "Edit song");
|
||||
# endif // HAVE_TAGLIB_H
|
||||
key(w, Type::EditDirectoryName, "Edit directory name");
|
||||
key(w, Type::EditPlaylistName, "Edit playlist name");
|
||||
key(w, Type::ChangeBrowseMode, "Browse MPD database/local filesystem");
|
||||
key(w, Type::ToggleBrowserSortMode, "Toggle sort mode");
|
||||
key(w, Type::JumpToPlayingSong, "Locate playing song");
|
||||
key(w, Type::JumpToParentDirectory, "Jump to parent directory");
|
||||
key(w, Type::DeleteBrowserItems, "Delete selected items from disk");
|
||||
key(w, Type::JumpToPlaylistEditor, "Jump to playlist editor (playlists only)");
|
||||
|
||||
key_section(w, "Search engine");
|
||||
key(w, Type::RunAction, "Modify option / Run action");
|
||||
key(w, Type::AddItemToPlaylist, "Add item to playlist");
|
||||
key(w, Type::PlayItem, "Add item to playlist and play it");
|
||||
# ifdef HAVE_TAGLIB_H
|
||||
key(w, Type::EditSong, "Edit song");
|
||||
# endif // HAVE_TAGLIB_H
|
||||
key(w, Type::StartSearching, "Start searching");
|
||||
key(w, Type::ResetSearchEngine, "Reset search constraints and clear results");
|
||||
|
||||
key_section(w, "Media library");
|
||||
key(w, Type::ToggleMediaLibraryColumnsMode, "Switch between two/three columns mode");
|
||||
key(w, Type::PreviousColumn, "Previous column");
|
||||
key(w, Type::NextColumn, "Next column");
|
||||
key(w, Type::PlayItem, "Add item to playlist and play it");
|
||||
key(w, Type::AddItemToPlaylist, "Add item to playlist");
|
||||
# ifdef HAVE_TAGLIB_H
|
||||
key(w, Type::EditSong, "Edit song");
|
||||
# endif // HAVE_TAGLIB_H
|
||||
key(w, Type::EditLibraryTag, "Edit tag (left column)/album (middle/right column)");
|
||||
key(w, Type::ToggleLibraryTagType, "Toggle type of tag used in left column");
|
||||
key(w, Type::ToggleMediaLibrarySortMode, "Toggle sort mode");
|
||||
|
||||
key_section(w, "Playlist editor");
|
||||
key(w, Type::PreviousColumn, "Previous column");
|
||||
key(w, Type::NextColumn, "Next column");
|
||||
key(w, Type::PlayItem, "Add item to playlist and play it");
|
||||
key(w, Type::AddItemToPlaylist, "Add item to playlist");
|
||||
# ifdef HAVE_TAGLIB_H
|
||||
key(w, Type::EditSong, "Edit song");
|
||||
# endif // HAVE_TAGLIB_H
|
||||
key(w, Type::EditPlaylistName, "Edit playlist name");
|
||||
key(w, Type::MoveSelectedItemsUp, "Move selected item(s) up");
|
||||
key(w, Type::MoveSelectedItemsDown, "Move selected item(s) down");
|
||||
key(w, Type::DeleteStoredPlaylist, "Delete selected playlists (left column)");
|
||||
key(w, Type::DeletePlaylistItems, "Delete selected item(s) from playlist (right column)");
|
||||
key(w, Type::ClearPlaylist, "Clear playlist");
|
||||
key(w, Type::CropPlaylist, "Clear playlist except selected items");
|
||||
|
||||
key_section(w, "Lyrics");
|
||||
key(w, Type::ToggleLyricsUpdateOnSongChange, "Toggle lyrics update on song change");
|
||||
key(w, Type::EditLyrics, "Open lyrics in external editor");
|
||||
key(w, Type::RefetchLyrics, "Refetch lyrics");
|
||||
|
||||
# ifdef HAVE_TAGLIB_H
|
||||
key_section(w, "Tiny tag editor");
|
||||
key(w, Type::RunAction, "Edit tag / Run action");
|
||||
key(w, Type::SaveTagChanges, "Save");
|
||||
|
||||
key_section(w, "Tag editor");
|
||||
key(w, Type::EnterDirectory, "Enter directory (right column)");
|
||||
key(w, Type::RunAction, "Perform operation on selected items (middle column)");
|
||||
key(w, Type::RunAction, "Edit item (left column)");
|
||||
key(w, Type::PreviousColumn, "Previous column");
|
||||
key(w, Type::NextColumn, "Next column");
|
||||
key(w, Type::JumpToParentDirectory, "Jump to parent directory (left column, directories view)");
|
||||
# endif // HAVE_TAGLIB_H
|
||||
|
||||
# ifdef ENABLE_OUTPUTS
|
||||
key_section(w, "Outputs");
|
||||
key(w, Type::ToggleOutput, "Toggle output");
|
||||
# endif // ENABLE_OUTPUTS
|
||||
|
||||
# if defined(ENABLE_VISUALIZER) && defined(HAVE_FFTW3_H)
|
||||
key_section(w, "Music visualizer");
|
||||
key(w, Type::ToggleVisualizationType, "Toggle visualization type");
|
||||
# endif // ENABLE_VISUALIZER && HAVE_FFTW3_H
|
||||
|
||||
mouse_section(w, "Global");
|
||||
mouse(w, "Left click on \"Playing/Paused\"", "Play/pause");
|
||||
mouse(w, "Left click on progressbar", "Jump to pointed position in playing song");
|
||||
w << '\n';
|
||||
mouse(w, "Mouse wheel on \"Volume: xx\"", "Adjust volume");
|
||||
mouse(w, "Mouse wheel on main window", "Scroll");
|
||||
|
||||
mouse_section(w, "Playlist");
|
||||
mouse(w, "Left click", "Select pointed item");
|
||||
mouse(w, "Right click", "Play");
|
||||
|
||||
mouse_section(w, "Browser");
|
||||
mouse(w, "Left click on directory", "Enter pointed directory");
|
||||
mouse(w, "Right click on directory", "Add pointed directory to playlist");
|
||||
w << '\n';
|
||||
mouse(w, "Left click on song/playlist", "Add pointed item to playlist");
|
||||
mouse(w, "Right click on song/playlist", "Add pointed item to playlist and play it");
|
||||
|
||||
mouse_section(w, "Search engine");
|
||||
mouse(w, "Left click", "Highlight/switch value");
|
||||
mouse(w, "Right click", "Change value");
|
||||
|
||||
mouse_section(w, "Media library");
|
||||
mouse_column(w, "Left/middle");
|
||||
mouse(w, "Left click", "Select pointed item", true);
|
||||
mouse(w, "Right click", "Add item to playlist", true);
|
||||
w << '\n';
|
||||
mouse_column(w, "Right");
|
||||
mouse(w, "Left Click", "Add pointed item to playlist", true);
|
||||
mouse(w, "Right Click", "Add pointed item to playlist and play it", true);
|
||||
|
||||
mouse_section(w, "Playlist editor");
|
||||
mouse_column(w, "Left");
|
||||
mouse(w, "Left click", "Select pointed item", true);
|
||||
mouse(w, "Right click", "Add item to playlist", true);
|
||||
w << '\n';
|
||||
mouse_column(w, "Right");
|
||||
mouse(w, "Left click", "Add pointed item to playlist", true);
|
||||
mouse(w, "Right click", "Add pointed item to playlist and play it", true);
|
||||
|
||||
# ifdef HAVE_TAGLIB_H
|
||||
mouse_section(w, "Tiny tag editor");
|
||||
mouse(w, "Left click", "Select option");
|
||||
mouse(w, "Right click", "Set value/execute");
|
||||
|
||||
mouse_section(w, "Tag editor");
|
||||
mouse_column(w, "Left");
|
||||
mouse(w, "Left click", "Enter pointed directory/select pointed album", true);
|
||||
mouse(w, "Right click", "Toggle view (directories/albums)", true);
|
||||
w << '\n';
|
||||
mouse_column(w, "Middle");
|
||||
mouse(w, "Left click", "Select option", true);
|
||||
mouse(w, "Right click", "Set value/execute", true);
|
||||
w << '\n';
|
||||
mouse_column(w, "Right");
|
||||
mouse(w, "Left click", "Select pointed item", true);
|
||||
mouse(w, "Right click", "Set value", true);
|
||||
# endif // HAVE_TAGLIB_H
|
||||
|
||||
# ifdef ENABLE_OUTPUTS
|
||||
mouse_section(w, "Outputs");
|
||||
mouse(w, "Left click", "Select pointed output");
|
||||
mouse(w, "Right click", "Toggle output");
|
||||
# endif // ENABLE_OUTPUTS
|
||||
|
||||
section(w, "", "Action chains");
|
||||
for (const auto &k : Bindings)
|
||||
{
|
||||
for (const auto &binding : k.second)
|
||||
{
|
||||
if (!binding.isSingle())
|
||||
{
|
||||
std::vector<std::string> commands;
|
||||
for (const auto &action : binding.actions())
|
||||
commands.push_back(action->name());
|
||||
key(w, k.first, join<std::string>(commands, ", "));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section(w, "", "List of available colors");
|
||||
for (int i = 0; i < COLORS; ++i)
|
||||
w << NC::Color(i, NC::Color::transparent) << i+1 << NC::Color::End << " ";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Help::Help()
|
||||
: Screen(NC::Scrollpad(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border()))
|
||||
{
|
||||
write_bindings(w);
|
||||
w.flush();
|
||||
}
|
||||
|
||||
void Help::resize()
|
||||
{
|
||||
size_t x_offset, width;
|
||||
getWindowResizeParams(x_offset, width);
|
||||
w.resize(width, MainHeight);
|
||||
w.moveTo(x_offset, MainStartY);
|
||||
hasToBeResized = 0;
|
||||
}
|
||||
|
||||
void Help::switchTo()
|
||||
{
|
||||
SwitchTo::execute(this);
|
||||
drawHeader();
|
||||
}
|
||||
|
||||
std::wstring Help::title()
|
||||
{
|
||||
return L"Help";
|
||||
}
|
||||
47
src/screens/help.h
Normal file
47
src/screens/help.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_HELP_H
|
||||
#define NCMPCPP_HELP_H
|
||||
|
||||
#include "actions.h"
|
||||
#include "interfaces.h"
|
||||
#include "screens/screen.h"
|
||||
|
||||
struct Help: Screen<NC::Scrollpad>, Tabbable
|
||||
{
|
||||
Help();
|
||||
|
||||
virtual void resize() override;
|
||||
virtual void switchTo() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::Help; }
|
||||
|
||||
virtual void update() override { }
|
||||
|
||||
virtual bool isLockable() override { return true; }
|
||||
virtual bool isMergable() override { return true; }
|
||||
};
|
||||
|
||||
extern Help *myHelp;
|
||||
|
||||
#endif // NCMPCPP_HELP_H
|
||||
|
||||
86
src/screens/lastfm.cpp
Normal file
86
src/screens/lastfm.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "screens/lastfm.h"
|
||||
|
||||
#include "helpers.h"
|
||||
#include "charset.h"
|
||||
#include "global.h"
|
||||
#include "statusbar.h"
|
||||
#include "title.h"
|
||||
#include "screens/screen_switcher.h"
|
||||
|
||||
using Global::MainHeight;
|
||||
using Global::MainStartY;
|
||||
|
||||
Lastfm *myLastfm;
|
||||
|
||||
Lastfm::Lastfm()
|
||||
: Screen(NC::Scrollpad(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border()))
|
||||
{ }
|
||||
|
||||
void Lastfm::resize()
|
||||
{
|
||||
size_t x_offset, width;
|
||||
getWindowResizeParams(x_offset, width);
|
||||
w.resize(width, MainHeight);
|
||||
w.moveTo(x_offset, MainStartY);
|
||||
hasToBeResized = 0;
|
||||
}
|
||||
|
||||
std::wstring Lastfm::title()
|
||||
{
|
||||
return m_title;
|
||||
}
|
||||
|
||||
void Lastfm::update()
|
||||
{
|
||||
if (m_worker.valid() && m_worker.is_ready())
|
||||
getResult();
|
||||
}
|
||||
|
||||
void Lastfm::switchTo()
|
||||
{
|
||||
using Global::myScreen;
|
||||
if (myScreen != this)
|
||||
{
|
||||
SwitchTo::execute(this);
|
||||
drawHeader();
|
||||
}
|
||||
else
|
||||
switchToPreviousScreen();
|
||||
}
|
||||
|
||||
void Lastfm::getResult()
|
||||
{
|
||||
auto result = m_worker.get();
|
||||
if (result.first)
|
||||
{
|
||||
w.clear();
|
||||
w << Charset::utf8ToLocale(result.second);
|
||||
m_service->beautifyOutput(w);
|
||||
}
|
||||
else
|
||||
w << " " << NC::Color::Red << result.second << NC::Color::End;
|
||||
w.flush();
|
||||
w.refresh();
|
||||
// reset m_worker so it's no longer valid
|
||||
m_worker = boost::BOOST_THREAD_FUTURE<LastFm::Service::Result>();
|
||||
}
|
||||
79
src/screens/lastfm.h
Normal file
79
src/screens/lastfm.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_LASTFM_H
|
||||
#define NCMPCPP_LASTFM_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <boost/thread/future.hpp>
|
||||
#include <memory>
|
||||
|
||||
#include "interfaces.h"
|
||||
#include "lastfm_service.h"
|
||||
#include "screens/screen.h"
|
||||
#include "utility/wide_string.h"
|
||||
|
||||
struct Lastfm: Screen<NC::Scrollpad>, Tabbable
|
||||
{
|
||||
Lastfm();
|
||||
|
||||
virtual void switchTo() override;
|
||||
virtual void resize() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::Lastfm; }
|
||||
|
||||
virtual void update() override;
|
||||
|
||||
virtual bool isLockable() override { return false; }
|
||||
virtual bool isMergable() override { return true; }
|
||||
|
||||
template <typename ServiceT>
|
||||
void queueJob(ServiceT *service)
|
||||
{
|
||||
auto old_service = dynamic_cast<ServiceT *>(m_service.get());
|
||||
// if the same service and arguments were used, leave old info
|
||||
if (old_service != nullptr && *old_service == *service)
|
||||
return;
|
||||
|
||||
m_service = std::shared_ptr<ServiceT>(service);
|
||||
m_worker = boost::async(
|
||||
boost::launch::async,
|
||||
std::bind(&LastFm::Service::fetch, m_service));
|
||||
|
||||
w.clear();
|
||||
w << "Fetching information...";
|
||||
w.flush();
|
||||
m_title = ToWString(m_service->name());
|
||||
}
|
||||
|
||||
private:
|
||||
void getResult();
|
||||
|
||||
std::wstring m_title;
|
||||
|
||||
std::shared_ptr<LastFm::Service> m_service;
|
||||
boost::BOOST_THREAD_FUTURE<LastFm::Service::Result> m_worker;
|
||||
};
|
||||
|
||||
extern Lastfm *myLastfm;
|
||||
|
||||
#endif // NCMPCPP_LASTFM_H
|
||||
439
src/screens/lyrics.cpp
Normal file
439
src/screens/lyrics.cpp
Normal file
@@ -0,0 +1,439 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
#include <cassert>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
|
||||
#include "curses/scrollpad.h"
|
||||
#include "screens/browser.h"
|
||||
#include "charset.h"
|
||||
#include "curl_handle.h"
|
||||
#include "format_impl.h"
|
||||
#include "global.h"
|
||||
#include "helpers.h"
|
||||
#include "screens/lyrics.h"
|
||||
#include "screens/playlist.h"
|
||||
#include "settings.h"
|
||||
#include "song.h"
|
||||
#include "statusbar.h"
|
||||
#include "title.h"
|
||||
#include "screens/screen_switcher.h"
|
||||
#include "utility/string.h"
|
||||
|
||||
using Global::MainHeight;
|
||||
using Global::MainStartY;
|
||||
|
||||
Lyrics *myLyrics;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string removeExtension(std::string filename)
|
||||
{
|
||||
size_t dot = filename.rfind('.');
|
||||
if (dot != std::string::npos)
|
||||
filename.resize(dot);
|
||||
return filename;
|
||||
}
|
||||
|
||||
std::string lyricsFilename(const MPD::Song &s)
|
||||
{
|
||||
std::string filename;
|
||||
if (Config.store_lyrics_in_song_dir && !s.isStream())
|
||||
{
|
||||
if (s.isFromDatabase())
|
||||
filename = Config.mpd_music_dir + "/";
|
||||
filename += removeExtension(s.getURI());
|
||||
removeExtension(filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string artist = s.getArtist();
|
||||
std::string title = s.getTitle();
|
||||
if (artist.empty() || title.empty())
|
||||
filename = removeExtension(s.getName());
|
||||
else
|
||||
filename = artist + " - " + title;
|
||||
removeInvalidCharsFromFilename(filename, Config.generate_win32_compatible_filenames);
|
||||
filename = Config.lyrics_directory + "/" + filename;
|
||||
}
|
||||
filename += ".txt";
|
||||
return filename;
|
||||
}
|
||||
|
||||
bool loadLyrics(NC::Scrollpad &w, const std::string &filename)
|
||||
{
|
||||
std::ifstream input(filename);
|
||||
if (input.is_open())
|
||||
{
|
||||
std::string line;
|
||||
bool first_line = true;
|
||||
while (std::getline(input, line))
|
||||
{
|
||||
// Remove carriage returns as they mess up the display.
|
||||
boost::remove_erase(line, '\r');
|
||||
if (!first_line)
|
||||
w << '\n';
|
||||
w << Charset::utf8ToLocale(line);
|
||||
first_line = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool saveLyrics(const std::string &filename, const std::string &lyrics)
|
||||
{
|
||||
std::ofstream output(filename);
|
||||
if (output.is_open())
|
||||
{
|
||||
output << lyrics;
|
||||
output.close();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
boost::optional<std::string> downloadLyrics(
|
||||
const MPD::Song &s,
|
||||
std::shared_ptr<Shared<NC::Buffer>> shared_buffer,
|
||||
std::shared_ptr<std::atomic<bool>> download_stopper,
|
||||
LyricsFetcher *current_fetcher)
|
||||
{
|
||||
std::string s_artist = Curl::escape(s.getArtist());
|
||||
std::string s_title = Curl::escape(s.getTitle());
|
||||
// If artist or title is empty, use filename. This should give reasonable
|
||||
// results for google search based lyrics fetchers.
|
||||
if (s_artist.empty() || s_title.empty())
|
||||
{
|
||||
s_artist.clear();
|
||||
s_title = s.getName();
|
||||
// Get rid of underscores to improve search results.
|
||||
std::replace_if(s_title.begin(), s_title.end(), boost::is_any_of("-_"), ' ');
|
||||
size_t dot = s_title.rfind('.');
|
||||
if (dot != std::string::npos)
|
||||
s_title.resize(dot);
|
||||
s_title = Curl::escape(s_title);
|
||||
}
|
||||
|
||||
auto fetch_lyrics = [&](auto &fetcher_) {
|
||||
{
|
||||
if (shared_buffer)
|
||||
{
|
||||
auto buf = shared_buffer->acquire();
|
||||
*buf << "Fetching lyrics from "
|
||||
<< NC::Format::Bold
|
||||
<< fetcher_->name()
|
||||
<< NC::Format::NoBold << "... ";
|
||||
}
|
||||
}
|
||||
auto result_ = fetcher_->fetch(s_artist, s_title);
|
||||
if (result_.first == false)
|
||||
{
|
||||
if (shared_buffer)
|
||||
{
|
||||
auto buf = shared_buffer->acquire();
|
||||
*buf << NC::Color::Red
|
||||
<< result_.second
|
||||
<< NC::Color::End
|
||||
<< '\n';
|
||||
}
|
||||
}
|
||||
return result_;
|
||||
};
|
||||
|
||||
LyricsFetcher::Result fetcher_result;
|
||||
if (current_fetcher == nullptr)
|
||||
{
|
||||
for (auto &fetcher : Config.lyrics_fetchers)
|
||||
{
|
||||
if (download_stopper && download_stopper->load())
|
||||
return boost::none;
|
||||
fetcher_result = fetch_lyrics(fetcher);
|
||||
if (fetcher_result.first)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
fetcher_result = fetch_lyrics(current_fetcher);
|
||||
|
||||
boost::optional<std::string> result;
|
||||
if (fetcher_result.first)
|
||||
result = std::move(fetcher_result.second);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Lyrics::Lyrics()
|
||||
: Screen(NC::Scrollpad(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border()))
|
||||
, m_refresh_window(false)
|
||||
, m_scroll_begin(0)
|
||||
, m_fetcher(nullptr)
|
||||
{ }
|
||||
|
||||
void Lyrics::resize()
|
||||
{
|
||||
size_t x_offset, width;
|
||||
getWindowResizeParams(x_offset, width);
|
||||
w.resize(width, MainHeight);
|
||||
w.moveTo(x_offset, MainStartY);
|
||||
hasToBeResized = 0;
|
||||
}
|
||||
|
||||
void Lyrics::update()
|
||||
{
|
||||
if (m_worker.valid())
|
||||
{
|
||||
auto buffer = m_shared_buffer->acquire();
|
||||
if (!buffer->empty())
|
||||
{
|
||||
w << *buffer;
|
||||
buffer->clear();
|
||||
m_refresh_window = true;
|
||||
}
|
||||
|
||||
if (m_worker.is_ready())
|
||||
{
|
||||
auto lyrics = m_worker.get();
|
||||
if (lyrics)
|
||||
{
|
||||
w.clear();
|
||||
w << Charset::utf8ToLocale(*lyrics);
|
||||
std::string filename = lyricsFilename(m_song);
|
||||
if (!saveLyrics(filename, *lyrics))
|
||||
Statusbar::printf("Couldn't save lyrics as \"%1%\": %2%",
|
||||
filename, strerror(errno));
|
||||
}
|
||||
else
|
||||
w << "\nLyrics were not found.\n";
|
||||
clearWorker();
|
||||
m_refresh_window = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_refresh_window)
|
||||
{
|
||||
m_refresh_window = false;
|
||||
w.flush();
|
||||
w.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void Lyrics::switchTo()
|
||||
{
|
||||
using Global::myScreen;
|
||||
if (myScreen != this)
|
||||
{
|
||||
SwitchTo::execute(this);
|
||||
m_scroll_begin = 0;
|
||||
drawHeader();
|
||||
}
|
||||
else
|
||||
switchToPreviousScreen();
|
||||
}
|
||||
|
||||
std::wstring Lyrics::title()
|
||||
{
|
||||
std::wstring result = L"Lyrics: ";
|
||||
result += Scroller(
|
||||
Format::stringify<wchar_t>(Format::parse(L"{%a - %t}|{%f}"), &m_song),
|
||||
m_scroll_begin,
|
||||
COLS - result.length() - (Config.design == Design::Alternative
|
||||
? 2
|
||||
: Global::VolumeState.length()));
|
||||
return result;
|
||||
}
|
||||
|
||||
void Lyrics::fetch(const MPD::Song &s)
|
||||
{
|
||||
if (!m_worker.valid() || s != m_song)
|
||||
{
|
||||
stopDownload();
|
||||
w.clear();
|
||||
m_song = s;
|
||||
if (loadLyrics(w, lyricsFilename(m_song)))
|
||||
{
|
||||
clearWorker();
|
||||
m_refresh_window = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_download_stopper = std::make_shared<std::atomic<bool>>(false);
|
||||
m_shared_buffer = std::make_shared<Shared<NC::Buffer>>();
|
||||
m_worker = boost::async(
|
||||
boost::launch::async,
|
||||
std::bind(downloadLyrics,
|
||||
m_song, m_shared_buffer, m_download_stopper, m_fetcher));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Lyrics::refetchCurrent()
|
||||
{
|
||||
std::string filename = lyricsFilename(m_song);
|
||||
if (std::remove(filename.c_str()) == -1 && errno != ENOENT)
|
||||
{
|
||||
const char msg[] = "Couldn't remove \"%1%\": %2%";
|
||||
Statusbar::printf(msg, wideShorten(filename, COLS - const_strlen(msg) - 25),
|
||||
strerror(errno));
|
||||
}
|
||||
else
|
||||
{
|
||||
clearWorker();
|
||||
fetch(m_song);
|
||||
}
|
||||
}
|
||||
|
||||
void Lyrics::edit()
|
||||
{
|
||||
if (Config.external_editor.empty())
|
||||
{
|
||||
Statusbar::print("external_editor variable has to be set in configuration file");
|
||||
return;
|
||||
}
|
||||
|
||||
Statusbar::print("Opening lyrics in external editor...");
|
||||
|
||||
GNUC_UNUSED int res;
|
||||
std::string command;
|
||||
std::string filename = lyricsFilename(m_song);
|
||||
if (Config.use_console_editor)
|
||||
{
|
||||
command = "/bin/sh -c \"" + Config.external_editor + " \\\"" + filename + "\\\"\"";
|
||||
res = system(command.c_str());
|
||||
fetch(m_song);
|
||||
// Reset ncurses state to refresh the screen.
|
||||
endwin();
|
||||
initscr();
|
||||
curs_set(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
command = "nohup " + Config.external_editor
|
||||
+ " \"" + filename + "\" > /dev/null 2>&1 &";
|
||||
res = system(command.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Lyrics::toggleFetcher()
|
||||
{
|
||||
if (m_fetcher != nullptr)
|
||||
{
|
||||
auto fetcher = std::find_if(Config.lyrics_fetchers.begin(),
|
||||
Config.lyrics_fetchers.end(),
|
||||
[this](auto &f) { return f.get() == m_fetcher; });
|
||||
assert(fetcher != Config.lyrics_fetchers.end());
|
||||
++fetcher;
|
||||
if (fetcher != Config.lyrics_fetchers.end())
|
||||
m_fetcher = fetcher->get();
|
||||
else
|
||||
m_fetcher = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(!Config.lyrics_fetchers.empty());
|
||||
m_fetcher = Config.lyrics_fetchers[0].get();
|
||||
}
|
||||
|
||||
if (m_fetcher != nullptr)
|
||||
Statusbar::printf("Using lyrics fetcher: %s", m_fetcher->name());
|
||||
else
|
||||
Statusbar::print("Using all lyrics fetchers");
|
||||
}
|
||||
|
||||
void Lyrics::fetchInBackground(const MPD::Song &s, bool notify_)
|
||||
{
|
||||
auto consumer_impl = [this] {
|
||||
std::string lyrics_file;
|
||||
while (true)
|
||||
{
|
||||
ConsumerState::Song cs;
|
||||
{
|
||||
auto consumer = m_consumer_state.acquire();
|
||||
assert(consumer->running);
|
||||
if (consumer->songs.empty())
|
||||
{
|
||||
consumer->running = false;
|
||||
break;
|
||||
}
|
||||
lyrics_file = lyricsFilename(consumer->songs.front().song());
|
||||
if (!boost::filesystem::exists(lyrics_file))
|
||||
{
|
||||
cs = consumer->songs.front();
|
||||
if (cs.notify())
|
||||
{
|
||||
consumer->message = "Fetching lyrics for \""
|
||||
+ Format::stringify<char>(Config.song_status_format, &cs.song())
|
||||
+ "\"...";
|
||||
}
|
||||
}
|
||||
consumer->songs.pop();
|
||||
}
|
||||
if (!cs.song().empty())
|
||||
{
|
||||
auto lyrics = downloadLyrics(cs.song(), nullptr, nullptr, m_fetcher);
|
||||
if (lyrics)
|
||||
saveLyrics(lyrics_file, *lyrics);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto consumer = m_consumer_state.acquire();
|
||||
consumer->songs.emplace(s, notify_);
|
||||
// Start the consumer if it's not running.
|
||||
if (!consumer->running)
|
||||
{
|
||||
std::thread t(consumer_impl);
|
||||
t.detach();
|
||||
consumer->running = true;
|
||||
}
|
||||
}
|
||||
|
||||
boost::optional<std::string> Lyrics::tryTakeConsumerMessage()
|
||||
{
|
||||
boost::optional<std::string> result;
|
||||
auto consumer = m_consumer_state.acquire();
|
||||
if (consumer->message)
|
||||
{
|
||||
result = std::move(consumer->message);
|
||||
consumer->message = boost::none;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Lyrics::clearWorker()
|
||||
{
|
||||
m_shared_buffer.reset();
|
||||
m_worker = boost::BOOST_THREAD_FUTURE<boost::optional<std::string>>();
|
||||
}
|
||||
|
||||
void Lyrics::stopDownload()
|
||||
{
|
||||
if (m_download_stopper)
|
||||
m_download_stopper->store(true);
|
||||
}
|
||||
109
src/screens/lyrics.h
Normal file
109
src/screens/lyrics.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_LYRICS_H
|
||||
#define NCMPCPP_LYRICS_H
|
||||
|
||||
#include <atomic>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/thread/future.hpp>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
|
||||
#include "interfaces.h"
|
||||
#include "lyrics_fetcher.h"
|
||||
#include "screens/screen.h"
|
||||
#include "song.h"
|
||||
#include "utility/shared_resource.h"
|
||||
|
||||
struct Lyrics: Screen<NC::Scrollpad>, Tabbable
|
||||
{
|
||||
Lyrics();
|
||||
|
||||
// Screen<NC::Scrollpad> implementation
|
||||
virtual void resize() override;
|
||||
virtual void switchTo() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::Lyrics; }
|
||||
|
||||
virtual void update() override;
|
||||
|
||||
virtual bool isLockable() override { return false; }
|
||||
virtual bool isMergable() override { return true; }
|
||||
|
||||
// other members
|
||||
void fetch(const MPD::Song &s);
|
||||
void refetchCurrent();
|
||||
void edit();
|
||||
void toggleFetcher();
|
||||
|
||||
void fetchInBackground(const MPD::Song &s, bool notify_);
|
||||
boost::optional<std::string> tryTakeConsumerMessage();
|
||||
|
||||
private:
|
||||
struct ConsumerState
|
||||
{
|
||||
struct Song
|
||||
{
|
||||
Song()
|
||||
: m_notify(false)
|
||||
{ }
|
||||
|
||||
Song(const MPD::Song &s, bool notify_)
|
||||
: m_song(s), m_notify(notify_)
|
||||
{ }
|
||||
|
||||
const MPD::Song &song() const { return m_song; }
|
||||
bool notify() const { return m_notify; }
|
||||
|
||||
private:
|
||||
MPD::Song m_song;
|
||||
bool m_notify;
|
||||
};
|
||||
|
||||
ConsumerState()
|
||||
: running(false)
|
||||
{ }
|
||||
|
||||
bool running;
|
||||
std::queue<Song> songs;
|
||||
boost::optional<std::string> message;
|
||||
};
|
||||
|
||||
void clearWorker();
|
||||
void stopDownload();
|
||||
|
||||
bool m_refresh_window;
|
||||
size_t m_scroll_begin;
|
||||
|
||||
std::shared_ptr<Shared<NC::Buffer>> m_shared_buffer;
|
||||
std::shared_ptr<std::atomic<bool>> m_download_stopper;
|
||||
|
||||
MPD::Song m_song;
|
||||
LyricsFetcher *m_fetcher;
|
||||
boost::BOOST_THREAD_FUTURE<boost::optional<std::string>> m_worker;
|
||||
|
||||
Shared<ConsumerState> m_consumer_state;
|
||||
};
|
||||
|
||||
extern Lyrics *myLyrics;
|
||||
|
||||
#endif // NCMPCPP_LYRICS_H
|
||||
1153
src/screens/media_library.cpp
Normal file
1153
src/screens/media_library.cpp
Normal file
File diff suppressed because it is too large
Load Diff
160
src/screens/media_library.h
Normal file
160
src/screens/media_library.h
Normal file
@@ -0,0 +1,160 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_MEDIA_LIBRARY_H
|
||||
#define NCMPCPP_MEDIA_LIBRARY_H
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time_types.hpp>
|
||||
|
||||
#include "interfaces.h"
|
||||
#include "regex_filter.h"
|
||||
#include "screens/screen.h"
|
||||
#include "song_list.h"
|
||||
|
||||
struct MediaLibrary: Screen<NC::Window *>, Filterable, HasColumns, HasSongs, Searchable, Tabbable
|
||||
{
|
||||
MediaLibrary();
|
||||
|
||||
virtual void switchTo() override;
|
||||
virtual void resize() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::MediaLibrary; }
|
||||
|
||||
virtual void refresh() override;
|
||||
virtual void update() override;
|
||||
|
||||
virtual int windowTimeout() override;
|
||||
|
||||
virtual void mouseButtonPressed(MEVENT me) override;
|
||||
|
||||
virtual bool isLockable() override { return true; }
|
||||
virtual bool isMergable() override { return true; }
|
||||
|
||||
// Searchable implementation
|
||||
virtual bool allowsSearching() override;
|
||||
virtual const std::string &searchConstraint() override;
|
||||
virtual void setSearchConstraint(const std::string &constraint) override;
|
||||
virtual void clearSearchConstraint() override;
|
||||
virtual bool search(SearchDirection direction, bool wrap, bool skip_current) override;
|
||||
|
||||
// Filterable implementation
|
||||
virtual bool allowsFiltering() 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;
|
||||
|
||||
// HasColumns implementation
|
||||
virtual bool previousColumnAvailable() override;
|
||||
virtual void previousColumn() override;
|
||||
|
||||
virtual bool nextColumnAvailable() override;
|
||||
virtual void nextColumn() override;
|
||||
|
||||
// other members
|
||||
void updateTimer();
|
||||
void toggleColumnsMode();
|
||||
int columns();
|
||||
void locateSong(const MPD::Song &s);
|
||||
void toggleSortMode();
|
||||
|
||||
void requestTagsUpdate() { m_tags_update_request = true; }
|
||||
void requestAlbumsUpdate() { m_albums_update_request = true; }
|
||||
void requestSongsUpdate() { m_songs_update_request = true; }
|
||||
|
||||
struct PrimaryTag
|
||||
{
|
||||
PrimaryTag() : m_mtime(0) { }
|
||||
PrimaryTag(std::string tag_, time_t mtime_)
|
||||
: m_tag(std::move(tag_)), m_mtime(mtime_) { }
|
||||
|
||||
const std::string &tag() const { return m_tag; }
|
||||
time_t mtime() const { return m_mtime; }
|
||||
|
||||
private:
|
||||
std::string m_tag;
|
||||
time_t m_mtime;
|
||||
};
|
||||
|
||||
struct Album
|
||||
{
|
||||
Album(std::string tag_, std::string album_, std::string date_, time_t mtime_)
|
||||
: m_tag(std::move(tag_)), m_album(std::move(album_))
|
||||
, m_date(std::move(date_)), m_mtime(mtime_) { }
|
||||
|
||||
const std::string &tag() const { return m_tag; }
|
||||
const std::string &album() const { return m_album; }
|
||||
const std::string &date() const { return m_date; }
|
||||
time_t mtime() const { return m_mtime; }
|
||||
|
||||
private:
|
||||
std::string m_tag;
|
||||
std::string m_album;
|
||||
std::string m_date;
|
||||
time_t m_mtime;
|
||||
};
|
||||
|
||||
struct AlbumEntry
|
||||
{
|
||||
AlbumEntry() : m_all_tracks_entry(false), m_album("", "", "", 0) { }
|
||||
AlbumEntry(Album album_) : m_all_tracks_entry(false), m_album(album_) { }
|
||||
|
||||
const Album &entry() const { return m_album; }
|
||||
bool isAllTracksEntry() const { return m_all_tracks_entry; }
|
||||
|
||||
static AlbumEntry mkAllTracksEntry(std::string tag) {
|
||||
auto result = AlbumEntry(Album(tag, "", "", 0));
|
||||
result.m_all_tracks_entry = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_all_tracks_entry;
|
||||
Album m_album;
|
||||
};
|
||||
|
||||
NC::Menu<PrimaryTag> Tags;
|
||||
NC::Menu<AlbumEntry> Albums;
|
||||
SongMenu Songs;
|
||||
|
||||
private:
|
||||
bool m_tags_update_request;
|
||||
bool m_albums_update_request;
|
||||
bool m_songs_update_request;
|
||||
|
||||
boost::posix_time::ptime m_timer;
|
||||
|
||||
const int m_window_timeout;
|
||||
const boost::posix_time::time_duration m_fetching_delay;
|
||||
|
||||
Regex::Filter<PrimaryTag> m_tags_search_predicate;
|
||||
Regex::ItemFilter<AlbumEntry> m_albums_search_predicate;
|
||||
Regex::Filter<MPD::Song> m_songs_search_predicate;
|
||||
|
||||
};
|
||||
|
||||
extern MediaLibrary *myLibrary;
|
||||
|
||||
#endif // NCMPCPP_MEDIA_LIBRARY_H
|
||||
|
||||
114
src/screens/outputs.cpp
Normal file
114
src/screens/outputs.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "screens/outputs.h"
|
||||
|
||||
#ifdef ENABLE_OUTPUTS
|
||||
|
||||
#include "curses/menu_impl.h"
|
||||
#include "charset.h"
|
||||
#include "display.h"
|
||||
#include "global.h"
|
||||
#include "settings.h"
|
||||
#include "status.h"
|
||||
#include "statusbar.h"
|
||||
#include "title.h"
|
||||
#include "screens/screen_switcher.h"
|
||||
|
||||
using Global::MainHeight;
|
||||
using Global::MainStartY;
|
||||
using Global::myScreen;
|
||||
|
||||
Outputs *myOutputs;
|
||||
|
||||
Outputs::Outputs()
|
||||
: Screen(NC::Menu<MPD::Output>(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border()))
|
||||
{
|
||||
w.cyclicScrolling(Config.use_cyclic_scrolling);
|
||||
w.centeredCursor(Config.centered_cursor);
|
||||
w.setHighlightColor(Config.main_highlight_color);
|
||||
w.setItemDisplayer([](NC::Menu<MPD::Output> &menu) {
|
||||
menu << Charset::utf8ToLocale(menu.drawn()->value().name());
|
||||
});
|
||||
}
|
||||
|
||||
void Outputs::switchTo()
|
||||
{
|
||||
SwitchTo::execute(this);
|
||||
drawHeader();
|
||||
}
|
||||
|
||||
void Outputs::resize()
|
||||
{
|
||||
size_t x_offset, width;
|
||||
getWindowResizeParams(x_offset, width);
|
||||
w.resize(width, MainHeight);
|
||||
w.moveTo(x_offset, MainStartY);
|
||||
hasToBeResized = 0;
|
||||
}
|
||||
|
||||
std::wstring Outputs::title()
|
||||
{
|
||||
return L"Outputs";
|
||||
}
|
||||
|
||||
void Outputs::mouseButtonPressed(MEVENT me)
|
||||
{
|
||||
if (w.empty() || !w.hasCoords(me.x, me.y) || size_t(me.y) >= w.size())
|
||||
return;
|
||||
if (me.bstate & BUTTON1_PRESSED || me.bstate & BUTTON3_PRESSED)
|
||||
{
|
||||
w.Goto(me.y);
|
||||
if (me.bstate & BUTTON3_PRESSED)
|
||||
toggleOutput();
|
||||
}
|
||||
else
|
||||
Screen<WindowType>::mouseButtonPressed(me);
|
||||
}
|
||||
|
||||
void Outputs::fetchList()
|
||||
{
|
||||
w.clear();
|
||||
for (MPD::OutputIterator out = Mpd.GetOutputs(), end; out != end; ++out)
|
||||
{
|
||||
auto properties = NC::List::Properties::Selectable;
|
||||
if (out->enabled())
|
||||
properties |= NC::List::Properties::Bold;
|
||||
w.addItem(std::move(*out), properties);
|
||||
}
|
||||
if (myScreen == this)
|
||||
w.refresh();
|
||||
}
|
||||
|
||||
void Outputs::toggleOutput()
|
||||
{
|
||||
if (w.current()->value().enabled())
|
||||
{
|
||||
Mpd.DisableOutput(w.choice());
|
||||
Statusbar::printf("Output \"%s\" disabled", w.current()->value().name());
|
||||
}
|
||||
else
|
||||
{
|
||||
Mpd.EnableOutput(w.choice());
|
||||
Statusbar::printf("Output \"%s\" enabled", w.current()->value().name());
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ENABLE_OUTPUTS
|
||||
61
src/screens/outputs.h
Normal file
61
src/screens/outputs.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_OUTPUTS_H
|
||||
#define NCMPCPP_OUTPUTS_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef ENABLE_OUTPUTS
|
||||
|
||||
#include "interfaces.h"
|
||||
#include "menu.h"
|
||||
#include "mpdpp.h"
|
||||
#include "screens/screen.h"
|
||||
|
||||
struct Outputs: Screen<NC::Menu<MPD::Output>>, Tabbable
|
||||
{
|
||||
Outputs();
|
||||
|
||||
// Screen< NC::Menu<MPD::Output> > implementation
|
||||
virtual void switchTo() override;
|
||||
virtual void resize() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::Outputs; }
|
||||
|
||||
virtual void update() override { }
|
||||
|
||||
virtual void mouseButtonPressed(MEVENT me) override;
|
||||
|
||||
virtual bool isLockable() override { return true; }
|
||||
virtual bool isMergable() override { return true; }
|
||||
|
||||
// private members
|
||||
void fetchList();
|
||||
void toggleOutput();
|
||||
};
|
||||
|
||||
extern Outputs *myOutputs;
|
||||
|
||||
#endif // ENABLE_OUTPUTS
|
||||
|
||||
#endif // NCMPCPP_OUTPUTS_H
|
||||
|
||||
358
src/screens/playlist.cpp
Normal file
358
src/screens/playlist.cpp
Normal file
@@ -0,0 +1,358 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <sstream>
|
||||
|
||||
#include "curses/menu_impl.h"
|
||||
#include "display.h"
|
||||
#include "global.h"
|
||||
#include "helpers.h"
|
||||
#include "screens/playlist.h"
|
||||
#include "screens/screen_switcher.h"
|
||||
#include "song.h"
|
||||
#include "status.h"
|
||||
#include "statusbar.h"
|
||||
#include "helpers/song_iterator_maker.h"
|
||||
#include "utility/comparators.h"
|
||||
#include "utility/functional.h"
|
||||
#include "title.h"
|
||||
|
||||
using Global::MainHeight;
|
||||
using Global::MainStartY;
|
||||
|
||||
namespace ph = std::placeholders;
|
||||
|
||||
Playlist *myPlaylist;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string songToString(const MPD::Song &s);
|
||||
bool playlistEntryMatcher(const Regex::Regex &rx, const MPD::Song &s);
|
||||
|
||||
}
|
||||
|
||||
Playlist::Playlist()
|
||||
: m_total_length(0), m_remaining_time(0), m_scroll_begin(0)
|
||||
, m_timer(boost::posix_time::from_time_t(0))
|
||||
, m_reload_total_length(false), m_reload_remaining(false)
|
||||
{
|
||||
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());
|
||||
w.cyclicScrolling(Config.use_cyclic_scrolling);
|
||||
w.centeredCursor(Config.centered_cursor);
|
||||
w.setHighlightColor(Config.main_highlight_color);
|
||||
w.setSelectedPrefix(Config.selected_item_prefix);
|
||||
w.setSelectedSuffix(Config.selected_item_suffix);
|
||||
switch (Config.playlist_display_mode)
|
||||
{
|
||||
case DisplayMode::Classic:
|
||||
w.setItemDisplayer(std::bind(
|
||||
Display::Songs, ph::_1, std::cref(w), std::cref(Config.song_list_format)
|
||||
));
|
||||
break;
|
||||
case DisplayMode::Columns:
|
||||
w.setItemDisplayer(std::bind(
|
||||
Display::SongsInColumns, ph::_1, std::cref(w)
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::switchTo()
|
||||
{
|
||||
SwitchTo::execute(this);
|
||||
m_scroll_begin = 0;
|
||||
drawHeader();
|
||||
}
|
||||
|
||||
void Playlist::resize()
|
||||
{
|
||||
size_t x_offset, width;
|
||||
getWindowResizeParams(x_offset, width);
|
||||
w.resize(width, MainHeight);
|
||||
w.moveTo(x_offset, MainStartY);
|
||||
|
||||
switch (Config.playlist_display_mode)
|
||||
{
|
||||
case DisplayMode::Columns:
|
||||
if (Config.titles_visibility)
|
||||
{
|
||||
w.setTitle(Display::Columns(w.getWidth()));
|
||||
break;
|
||||
}
|
||||
case DisplayMode::Classic:
|
||||
w.setTitle("");
|
||||
}
|
||||
|
||||
hasToBeResized = 0;
|
||||
}
|
||||
|
||||
std::wstring Playlist::title()
|
||||
{
|
||||
std::wstring result = L"Playlist ";
|
||||
if (Config.playlist_show_mpd_host)
|
||||
{
|
||||
result += L"on ";
|
||||
result += ToWString(Mpd.GetHostname());
|
||||
result += L" ";
|
||||
}
|
||||
if (m_reload_total_length || m_reload_remaining)
|
||||
m_stats = getTotalLength();
|
||||
result += Scroller(ToWString(m_stats), m_scroll_begin, COLS-result.length()-(Config.design == Design::Alternative ? 2 : Global::VolumeState.length()));
|
||||
return result;
|
||||
}
|
||||
|
||||
void Playlist::update()
|
||||
{
|
||||
if (w.isHighlighted()
|
||||
&& Config.playlist_disable_highlight_delay.time_duration::seconds() > 0
|
||||
&& Global::Timer - m_timer > Config.playlist_disable_highlight_delay)
|
||||
{
|
||||
w.setHighlighting(false);
|
||||
w.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::mouseButtonPressed(MEVENT me)
|
||||
{
|
||||
if (!w.empty() && w.hasCoords(me.x, me.y))
|
||||
{
|
||||
if (size_t(me.y) < w.size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
|
||||
{
|
||||
w.Goto(me.y);
|
||||
if (me.bstate & BUTTON3_PRESSED)
|
||||
addItemToPlaylist(true);
|
||||
}
|
||||
else
|
||||
Screen<WindowType>::mouseButtonPressed(me);
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool Playlist::allowsSearching()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string &Playlist::searchConstraint()
|
||||
{
|
||||
return m_search_predicate.constraint();
|
||||
}
|
||||
|
||||
void Playlist::setSearchConstraint(const std::string &constraint)
|
||||
{
|
||||
m_search_predicate = Regex::Filter<MPD::Song>(
|
||||
constraint,
|
||||
Config.regex_type,
|
||||
playlistEntryMatcher);
|
||||
}
|
||||
|
||||
void Playlist::clearSearchConstraint()
|
||||
{
|
||||
m_search_predicate.clear();
|
||||
}
|
||||
|
||||
bool Playlist::search(SearchDirection direction, bool wrap, bool skip_current)
|
||||
{
|
||||
return ::search(w, m_search_predicate, direction, wrap, skip_current);
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool Playlist::allowsFiltering()
|
||||
{
|
||||
return allowsSearching();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
return !w.empty();
|
||||
}
|
||||
|
||||
bool Playlist::addItemToPlaylist(bool play)
|
||||
{
|
||||
if (play)
|
||||
Mpd.PlayID(w.currentV()->getID());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<MPD::Song> Playlist::getSelectedSongs()
|
||||
{
|
||||
return w.getSelectedSongs();
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
MPD::Song Playlist::nowPlayingSong()
|
||||
{
|
||||
MPD::Song s;
|
||||
if (Status::State::player() != MPD::psUnknown)
|
||||
{
|
||||
ScopedUnfilteredMenu<MPD::Song> sunfilter(ReapplyFilter::No, w);
|
||||
auto sp = Status::State::currentSongPosition();
|
||||
if (sp >= 0 && size_t(sp) < w.size())
|
||||
s = w.at(sp).value();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void Playlist::locateSong(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);
|
||||
m_timer = Global::Timer;
|
||||
}
|
||||
|
||||
std::string Playlist::getTotalLength()
|
||||
{
|
||||
std::ostringstream result;
|
||||
|
||||
if (m_reload_total_length)
|
||||
{
|
||||
m_total_length = 0;
|
||||
for (const auto &s : w)
|
||||
m_total_length += s.value().getDuration();
|
||||
m_reload_total_length = false;
|
||||
}
|
||||
if (Config.playlist_show_remaining_time && m_reload_remaining)
|
||||
{
|
||||
ScopedUnfilteredMenu<MPD::Song> sunfilter(ReapplyFilter::No, w);
|
||||
m_remaining_time = 0;
|
||||
for (size_t i = Status::State::currentSongPosition(); i < w.size(); ++i)
|
||||
m_remaining_time += w[i].value().getDuration();
|
||||
m_reload_remaining = false;
|
||||
}
|
||||
|
||||
result << '(' << w.size() << (w.size() == 1 ? " item" : " items");
|
||||
|
||||
if (w.isFiltered())
|
||||
{
|
||||
ScopedUnfilteredMenu<MPD::Song> sunfilter(ReapplyFilter::No, w);
|
||||
result << " (out of " << w.size() << ")";
|
||||
}
|
||||
|
||||
if (m_total_length)
|
||||
{
|
||||
result << ", length: ";
|
||||
ShowTime(result, m_total_length, Config.playlist_shorten_total_times);
|
||||
}
|
||||
if (Config.playlist_show_remaining_time && m_remaining_time && w.size() > 1)
|
||||
{
|
||||
result << ", remaining: ";
|
||||
ShowTime(result, m_remaining_time, Config.playlist_shorten_total_times);
|
||||
}
|
||||
result << ')';
|
||||
return result.str();
|
||||
}
|
||||
|
||||
void Playlist::setSelectedItemsPriority(int prio)
|
||||
{
|
||||
auto list = getSelectedOrCurrent(w.begin(), w.end(), w.current());
|
||||
Mpd.StartCommandsList();
|
||||
for (auto it = list.begin(); it != list.end(); ++it)
|
||||
Mpd.SetPriority((*it)->value(), prio);
|
||||
Mpd.CommitCommandsList();
|
||||
Statusbar::print("Priority set");
|
||||
}
|
||||
|
||||
bool Playlist::checkForSong(const MPD::Song &s)
|
||||
{
|
||||
return m_song_refs.find(s) != m_song_refs.end();
|
||||
}
|
||||
|
||||
void Playlist::registerSong(const MPD::Song &s)
|
||||
{
|
||||
++m_song_refs[s];
|
||||
}
|
||||
|
||||
void Playlist::unregisterSong(const MPD::Song &s)
|
||||
{
|
||||
auto it = m_song_refs.find(s);
|
||||
assert(it != m_song_refs.end());
|
||||
if (it->second == 1)
|
||||
m_song_refs.erase(it);
|
||||
else
|
||||
--it->second;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::string songToString(const MPD::Song &s)
|
||||
{
|
||||
std::string result;
|
||||
switch (Config.playlist_display_mode)
|
||||
{
|
||||
case DisplayMode::Classic:
|
||||
result = Format::stringify<char>(Config.song_list_format, &s);
|
||||
break;
|
||||
case DisplayMode::Columns:
|
||||
result = Format::stringify<char>(Config.song_columns_mode_format, &s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool playlistEntryMatcher(const Regex::Regex &rx, const MPD::Song &s)
|
||||
{
|
||||
return Regex::search(songToString(s), rx);
|
||||
}
|
||||
|
||||
}
|
||||
107
src/screens/playlist.h
Normal file
107
src/screens/playlist.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_PLAYLIST_H
|
||||
#define NCMPCPP_PLAYLIST_H
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time_types.hpp>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "interfaces.h"
|
||||
#include "regex_filter.h"
|
||||
#include "screens/screen.h"
|
||||
#include "song.h"
|
||||
#include "song_list.h"
|
||||
|
||||
struct Playlist: Screen<SongMenu>, Filterable, HasSongs, Searchable, Tabbable
|
||||
{
|
||||
Playlist();
|
||||
|
||||
// Screen<SongMenu> implementation
|
||||
virtual void switchTo() override;
|
||||
virtual void resize() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::Playlist; }
|
||||
|
||||
virtual void update() override;
|
||||
|
||||
virtual void mouseButtonPressed(MEVENT me) override;
|
||||
|
||||
virtual bool isLockable() override { return true; }
|
||||
virtual bool isMergable() override { return true; }
|
||||
|
||||
// Searchable implementation
|
||||
virtual bool allowsSearching() override;
|
||||
virtual const std::string &searchConstraint() override;
|
||||
virtual void setSearchConstraint(const std::string &constraint) override;
|
||||
virtual void clearSearchConstraint() override;
|
||||
virtual bool search(SearchDirection direction, bool wrap, bool skip_current) override;
|
||||
|
||||
// Filterable implementation
|
||||
virtual bool allowsFiltering() 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;
|
||||
|
||||
// other members
|
||||
MPD::Song nowPlayingSong();
|
||||
|
||||
// Locate song in playlist.
|
||||
void locateSong(const MPD::Song &s);
|
||||
|
||||
void enableHighlighting();
|
||||
|
||||
void setSelectedItemsPriority(int prio);
|
||||
|
||||
bool checkForSong(const MPD::Song &s);
|
||||
void registerSong(const MPD::Song &s);
|
||||
void unregisterSong(const MPD::Song &s);
|
||||
|
||||
void reloadTotalLength() { m_reload_total_length = true; }
|
||||
void reloadRemaining() { m_reload_remaining = true; }
|
||||
|
||||
private:
|
||||
std::string getTotalLength();
|
||||
|
||||
std::string m_stats;
|
||||
|
||||
std::unordered_map<MPD::Song, int, MPD::Song::Hash> m_song_refs;
|
||||
|
||||
size_t m_total_length;;
|
||||
size_t m_remaining_time;
|
||||
size_t m_scroll_begin;
|
||||
|
||||
boost::posix_time::ptime m_timer;
|
||||
|
||||
bool m_reload_total_length;
|
||||
bool m_reload_remaining;
|
||||
|
||||
Regex::Filter<MPD::Song> m_search_predicate;
|
||||
};
|
||||
|
||||
extern Playlist *myPlaylist;
|
||||
|
||||
#endif // NCMPCPP_PLAYLIST_H
|
||||
|
||||
521
src/screens/playlist_editor.cpp
Normal file
521
src/screens/playlist_editor.cpp
Normal file
@@ -0,0 +1,521 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/lambda/bind.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <cassert>
|
||||
|
||||
#include "curses/menu_impl.h"
|
||||
#include "charset.h"
|
||||
#include "display.h"
|
||||
#include "global.h"
|
||||
#include "helpers.h"
|
||||
#include "screens/playlist.h"
|
||||
#include "screens/playlist_editor.h"
|
||||
#include "mpdpp.h"
|
||||
#include "status.h"
|
||||
#include "statusbar.h"
|
||||
#include "screens/tag_editor.h"
|
||||
#include "helpers/song_iterator_maker.h"
|
||||
#include "utility/functional.h"
|
||||
#include "utility/comparators.h"
|
||||
#include "title.h"
|
||||
#include "screens/screen_switcher.h"
|
||||
|
||||
using Global::MainHeight;
|
||||
using Global::MainStartY;
|
||||
|
||||
namespace ph = std::placeholders;
|
||||
|
||||
PlaylistEditor *myPlaylistEditor;
|
||||
|
||||
namespace {
|
||||
|
||||
size_t LeftColumnStartX;
|
||||
size_t LeftColumnWidth;
|
||||
size_t RightColumnStartX;
|
||||
size_t RightColumnWidth;
|
||||
|
||||
std::string SongToString(const MPD::Song &s);
|
||||
bool PlaylistEntryMatcher(const Regex::Regex &rx, const MPD::Playlist &playlist);
|
||||
bool SongEntryMatcher(const Regex::Regex &rx, const MPD::Song &s);
|
||||
|
||||
}
|
||||
|
||||
PlaylistEditor::PlaylistEditor()
|
||||
: m_timer(boost::posix_time::from_time_t(0))
|
||||
, m_window_timeout(Config.data_fetching_delay ? 250 : BaseScreen::defaultWindowTimeout)
|
||||
, m_fetching_delay(boost::posix_time::milliseconds(Config.data_fetching_delay ? 250 : -1))
|
||||
{
|
||||
LeftColumnWidth = COLS/3-1;
|
||||
RightColumnStartX = LeftColumnWidth+1;
|
||||
RightColumnWidth = COLS-LeftColumnWidth-1;
|
||||
|
||||
Playlists = NC::Menu<MPD::Playlist>(0, MainStartY, LeftColumnWidth, MainHeight, Config.titles_visibility ? "Playlists" : "", Config.main_color, NC::Border());
|
||||
Playlists.setHighlightColor(Config.active_column_color);
|
||||
Playlists.cyclicScrolling(Config.use_cyclic_scrolling);
|
||||
Playlists.centeredCursor(Config.centered_cursor);
|
||||
Playlists.setSelectedPrefix(Config.selected_item_prefix);
|
||||
Playlists.setSelectedSuffix(Config.selected_item_suffix);
|
||||
Playlists.setItemDisplayer([](NC::Menu<MPD::Playlist> &menu) {
|
||||
menu << Charset::utf8ToLocale(menu.drawn()->value().path());
|
||||
});
|
||||
|
||||
Content = NC::Menu<MPD::Song>(RightColumnStartX, MainStartY, RightColumnWidth, MainHeight, Config.titles_visibility ? "Content" : "", Config.main_color, NC::Border());
|
||||
Content.setHighlightColor(Config.main_highlight_color);
|
||||
Content.cyclicScrolling(Config.use_cyclic_scrolling);
|
||||
Content.centeredCursor(Config.centered_cursor);
|
||||
Content.setSelectedPrefix(Config.selected_item_prefix);
|
||||
Content.setSelectedSuffix(Config.selected_item_suffix);
|
||||
switch (Config.playlist_editor_display_mode)
|
||||
{
|
||||
case DisplayMode::Classic:
|
||||
Content.setItemDisplayer(std::bind(
|
||||
Display::Songs, ph::_1, std::cref(Content), std::cref(Config.song_list_format)
|
||||
));
|
||||
break;
|
||||
case DisplayMode::Columns:
|
||||
Content.setItemDisplayer(std::bind(
|
||||
Display::SongsInColumns, ph::_1, std::cref(Content)
|
||||
));
|
||||
break;
|
||||
}
|
||||
|
||||
w = &Playlists;
|
||||
}
|
||||
|
||||
void PlaylistEditor::resize()
|
||||
{
|
||||
size_t x_offset, width;
|
||||
getWindowResizeParams(x_offset, width);
|
||||
|
||||
LeftColumnStartX = x_offset;
|
||||
LeftColumnWidth = width/3-1;
|
||||
RightColumnStartX = LeftColumnStartX+LeftColumnWidth+1;
|
||||
RightColumnWidth = width-LeftColumnWidth-1;
|
||||
|
||||
Playlists.resize(LeftColumnWidth, MainHeight);
|
||||
Content.resize(RightColumnWidth, MainHeight);
|
||||
|
||||
Playlists.moveTo(LeftColumnStartX, MainStartY);
|
||||
Content.moveTo(RightColumnStartX, MainStartY);
|
||||
|
||||
hasToBeResized = 0;
|
||||
}
|
||||
|
||||
std::wstring PlaylistEditor::title()
|
||||
{
|
||||
return L"Playlist editor";
|
||||
}
|
||||
|
||||
void PlaylistEditor::refresh()
|
||||
{
|
||||
Playlists.display();
|
||||
drawSeparator(RightColumnStartX-1);
|
||||
Content.display();
|
||||
}
|
||||
|
||||
void PlaylistEditor::switchTo()
|
||||
{
|
||||
SwitchTo::execute(this);
|
||||
markSongsInPlaylist(Content);
|
||||
drawHeader();
|
||||
refresh();
|
||||
}
|
||||
|
||||
void PlaylistEditor::update()
|
||||
{
|
||||
{
|
||||
ScopedUnfilteredMenu<MPD::Playlist> sunfilter_playlists(ReapplyFilter::No, Playlists);
|
||||
if (Playlists.empty() || m_playlists_update_requested)
|
||||
{
|
||||
m_playlists_update_requested = false;
|
||||
sunfilter_playlists.set(ReapplyFilter::Yes, true);
|
||||
size_t idx = 0;
|
||||
try
|
||||
{
|
||||
for (MPD::PlaylistIterator it = Mpd.GetPlaylists(), end; it != end; ++it, ++idx)
|
||||
{
|
||||
if (idx < Playlists.size())
|
||||
Playlists[idx].value() = std::move(*it);
|
||||
else
|
||||
Playlists.addItem(std::move(*it));
|
||||
};
|
||||
}
|
||||
catch (MPD::ServerError &e)
|
||||
{
|
||||
if (e.code() == MPD_SERVER_ERROR_SYSTEM) // no playlists directory
|
||||
Statusbar::print(e.what());
|
||||
else
|
||||
throw;
|
||||
}
|
||||
if (idx < Playlists.size())
|
||||
Playlists.resizeList(idx);
|
||||
std::sort(Playlists.beginV(), Playlists.endV(),
|
||||
LocaleBasedSorting(std::locale(), Config.ignore_leading_the));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ScopedUnfilteredMenu<MPD::Song> sunfilter_content(ReapplyFilter::No, Content);
|
||||
if (!Playlists.empty()
|
||||
&& ((Content.empty() && Global::Timer - m_timer > m_fetching_delay)
|
||||
|| m_content_update_requested))
|
||||
{
|
||||
m_content_update_requested = false;
|
||||
sunfilter_content.set(ReapplyFilter::Yes, true);
|
||||
size_t idx = 0;
|
||||
MPD::SongIterator s = Mpd.GetPlaylistContent(Playlists.current()->value().path()), end;
|
||||
for (; s != end; ++s, ++idx)
|
||||
{
|
||||
bool in_playlist = myPlaylist->checkForSong(*s);
|
||||
if (idx < Content.size())
|
||||
{
|
||||
Content[idx].setBold(in_playlist);
|
||||
Content[idx].value() = std::move(*s);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto properties = NC::List::Properties::Selectable;
|
||||
if (in_playlist)
|
||||
properties |= NC::List::Properties::Bold;
|
||||
Content.addItem(std::move(*s), properties);
|
||||
}
|
||||
}
|
||||
if (idx < Content.size())
|
||||
Content.resizeList(idx);
|
||||
std::string wtitle;
|
||||
if (Config.titles_visibility)
|
||||
{
|
||||
wtitle = (boost::format("Content (%1% %2%)")
|
||||
% boost::lexical_cast<std::string>(Content.size())
|
||||
% (Content.size() == 1 ? "item" : "items")).str();
|
||||
wtitle.resize(Content.getWidth());
|
||||
}
|
||||
Content.setTitle(wtitle);
|
||||
Content.refreshBorder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int PlaylistEditor::windowTimeout()
|
||||
{
|
||||
ScopedUnfilteredMenu<MPD::Song> sunfilter_content(ReapplyFilter::No, Content);
|
||||
if (Content.empty())
|
||||
return m_window_timeout;
|
||||
else
|
||||
return Screen<WindowType>::windowTimeout();
|
||||
}
|
||||
|
||||
void PlaylistEditor::mouseButtonPressed(MEVENT me)
|
||||
{
|
||||
if (Playlists.hasCoords(me.x, me.y))
|
||||
{
|
||||
if (!isActiveWindow(Playlists))
|
||||
{
|
||||
if (previousColumnAvailable())
|
||||
previousColumn();
|
||||
else
|
||||
return;
|
||||
}
|
||||
if (size_t(me.y) < Playlists.size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
|
||||
{
|
||||
Playlists.Goto(me.y);
|
||||
if (me.bstate & BUTTON3_PRESSED)
|
||||
addItemToPlaylist(false);
|
||||
}
|
||||
else
|
||||
Screen<WindowType>::mouseButtonPressed(me);
|
||||
Content.clear();
|
||||
}
|
||||
else if (Content.hasCoords(me.x, me.y))
|
||||
{
|
||||
if (!isActiveWindow(Content))
|
||||
{
|
||||
if (nextColumnAvailable())
|
||||
nextColumn();
|
||||
else
|
||||
return;
|
||||
}
|
||||
if (size_t(me.y) < Content.size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
|
||||
{
|
||||
Content.Goto(me.y);
|
||||
bool play = me.bstate & BUTTON3_PRESSED;
|
||||
addItemToPlaylist(play);
|
||||
}
|
||||
else
|
||||
Screen<WindowType>::mouseButtonPressed(me);
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool PlaylistEditor::allowsSearching()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string &PlaylistEditor::searchConstraint()
|
||||
{
|
||||
if (isActiveWindow(Playlists))
|
||||
return m_playlists_search_predicate.constraint();
|
||||
else if (isActiveWindow(Content))
|
||||
return m_content_search_predicate.constraint();
|
||||
throw std::runtime_error("no active window");
|
||||
}
|
||||
|
||||
void PlaylistEditor::setSearchConstraint(const std::string &constraint)
|
||||
{
|
||||
if (isActiveWindow(Playlists))
|
||||
{
|
||||
m_playlists_search_predicate = Regex::Filter<MPD::Playlist>(
|
||||
constraint,
|
||||
Config.regex_type,
|
||||
PlaylistEntryMatcher);
|
||||
}
|
||||
else if (isActiveWindow(Content))
|
||||
{
|
||||
m_content_search_predicate = Regex::Filter<MPD::Song>(
|
||||
constraint,
|
||||
Config.regex_type,
|
||||
SongEntryMatcher);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaylistEditor::clearSearchConstraint()
|
||||
{
|
||||
if (isActiveWindow(Playlists))
|
||||
m_playlists_search_predicate.clear();
|
||||
else if (isActiveWindow(Content))
|
||||
m_content_search_predicate.clear();
|
||||
}
|
||||
|
||||
bool PlaylistEditor::search(SearchDirection direction, bool wrap, bool skip_current)
|
||||
{
|
||||
bool result = false;
|
||||
if (isActiveWindow(Playlists))
|
||||
result = ::search(Playlists, m_playlists_search_predicate, direction, wrap, skip_current);
|
||||
else if (isActiveWindow(Content))
|
||||
result = ::search(Content, m_content_search_predicate, direction, wrap, skip_current);
|
||||
return result;
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool PlaylistEditor::allowsFiltering()
|
||||
{
|
||||
return allowsSearching();
|
||||
}
|
||||
|
||||
std::string PlaylistEditor::currentFilter()
|
||||
{
|
||||
std::string result;
|
||||
if (isActiveWindow(Playlists))
|
||||
{
|
||||
if (auto pred = Playlists.filterPredicate<Regex::Filter<MPD::Playlist>>())
|
||||
result = pred->constraint();
|
||||
}
|
||||
else if (isActiveWindow(Content))
|
||||
{
|
||||
if (auto pred = Content.filterPredicate<Regex::Filter<MPD::Song>>())
|
||||
result = pred->constraint();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void PlaylistEditor::applyFilter(const std::string &constraint)
|
||||
{
|
||||
if (isActiveWindow(Playlists))
|
||||
{
|
||||
if (!constraint.empty())
|
||||
{
|
||||
Playlists.applyFilter(Regex::Filter<MPD::Playlist>(
|
||||
constraint,
|
||||
Config.regex_type,
|
||||
PlaylistEntryMatcher));
|
||||
}
|
||||
else
|
||||
Playlists.clearFilter();
|
||||
}
|
||||
else if (isActiveWindow(Content))
|
||||
{
|
||||
if (!constraint.empty())
|
||||
{
|
||||
Content.applyFilter(Regex::Filter<MPD::Song>(
|
||||
constraint,
|
||||
Config.regex_type,
|
||||
SongEntryMatcher));
|
||||
}
|
||||
else
|
||||
Content.clearFilter();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool PlaylistEditor::itemAvailable()
|
||||
{
|
||||
if (isActiveWindow(Playlists))
|
||||
return !Playlists.empty();
|
||||
if (isActiveWindow(Content))
|
||||
return !Content.empty();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlaylistEditor::addItemToPlaylist(bool play)
|
||||
{
|
||||
bool result = false;
|
||||
if (isActiveWindow(Playlists))
|
||||
{
|
||||
ScopedUnfilteredMenu<MPD::Song> sunfilter_content(ReapplyFilter::No, Content);
|
||||
result = addSongsToPlaylist(Content.beginV(), Content.endV(), play, -1);
|
||||
Statusbar::printf("Playlist \"%1%\" loaded%2%",
|
||||
Playlists.current()->value().path(), withErrors(result));
|
||||
}
|
||||
else if (isActiveWindow(Content))
|
||||
result = addSongToPlaylist(Content.current()->value(), play);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<MPD::Song> PlaylistEditor::getSelectedSongs()
|
||||
{
|
||||
std::vector<MPD::Song> result;
|
||||
if (isActiveWindow(Playlists))
|
||||
{
|
||||
bool any_selected = false;
|
||||
for (auto &e : Playlists)
|
||||
{
|
||||
if (e.isSelected())
|
||||
{
|
||||
any_selected = true;
|
||||
std::copy(
|
||||
std::make_move_iterator(Mpd.GetPlaylistContent(e.value().path())),
|
||||
std::make_move_iterator(MPD::SongIterator()),
|
||||
std::back_inserter(result));
|
||||
}
|
||||
}
|
||||
// if no item is selected, add songs from right column
|
||||
ScopedUnfilteredMenu<MPD::Song> sunfilter_content(ReapplyFilter::No, Content);
|
||||
if (!any_selected && !Playlists.empty())
|
||||
std::copy(Content.beginV(), Content.endV(), std::back_inserter(result));
|
||||
}
|
||||
else if (isActiveWindow(Content))
|
||||
result = Content.getSelectedSongs();
|
||||
return result;
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool PlaylistEditor::previousColumnAvailable()
|
||||
{
|
||||
if (isActiveWindow(Content))
|
||||
{
|
||||
ScopedUnfilteredMenu<MPD::Playlist> sunfilter_playlists(ReapplyFilter::No, Playlists);
|
||||
if (!Playlists.empty())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PlaylistEditor::previousColumn()
|
||||
{
|
||||
if (isActiveWindow(Content))
|
||||
{
|
||||
Content.setHighlightColor(Config.main_highlight_color);
|
||||
w->refresh();
|
||||
w = &Playlists;
|
||||
Playlists.setHighlightColor(Config.active_column_color);
|
||||
}
|
||||
}
|
||||
|
||||
bool PlaylistEditor::nextColumnAvailable()
|
||||
{
|
||||
if (isActiveWindow(Playlists))
|
||||
{
|
||||
ScopedUnfilteredMenu<MPD::Song> sunfilter_content(ReapplyFilter::No, Content);
|
||||
if (!Content.empty())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PlaylistEditor::nextColumn()
|
||||
{
|
||||
if (isActiveWindow(Playlists))
|
||||
{
|
||||
Playlists.setHighlightColor(Config.main_highlight_color);
|
||||
w->refresh();
|
||||
w = &Content;
|
||||
Content.setHighlightColor(Config.active_column_color);
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
void PlaylistEditor::updateTimer()
|
||||
{
|
||||
m_timer = Global::Timer;
|
||||
}
|
||||
|
||||
void PlaylistEditor::locatePlaylist(const MPD::Playlist &playlist)
|
||||
{
|
||||
update();
|
||||
Playlists.clearFilter();
|
||||
auto first = Playlists.beginV(), last = Playlists.endV();
|
||||
auto it = std::find(first, last, playlist);
|
||||
if (it != last)
|
||||
{
|
||||
Playlists.highlight(it - first);
|
||||
Content.clear();
|
||||
Content.clearFilter();
|
||||
switchTo();
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::string SongToString(const MPD::Song &s)
|
||||
{
|
||||
std::string result;
|
||||
switch (Config.playlist_display_mode)
|
||||
{
|
||||
case DisplayMode::Classic:
|
||||
result = Format::stringify<char>(Config.song_list_format, &s);
|
||||
break;
|
||||
case DisplayMode::Columns:
|
||||
result = Format::stringify<char>(Config.song_columns_mode_format, &s);
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool PlaylistEntryMatcher(const Regex::Regex &rx, const MPD::Playlist &playlist)
|
||||
{
|
||||
return Regex::search(playlist.path(), rx);
|
||||
}
|
||||
|
||||
bool SongEntryMatcher(const Regex::Regex &rx, const MPD::Song &s)
|
||||
{
|
||||
return Regex::search(SongToString(s), rx);
|
||||
}
|
||||
|
||||
}
|
||||
102
src/screens/playlist_editor.h
Normal file
102
src/screens/playlist_editor.h
Normal file
@@ -0,0 +1,102 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_PLAYLIST_EDITOR_H
|
||||
#define NCMPCPP_PLAYLIST_EDITOR_H
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time_types.hpp>
|
||||
|
||||
#include "interfaces.h"
|
||||
#include "regex_filter.h"
|
||||
#include "screens/screen.h"
|
||||
#include "song_list.h"
|
||||
|
||||
struct PlaylistEditor: Screen<NC::Window *>, Filterable, HasColumns, HasSongs, Searchable, Tabbable
|
||||
{
|
||||
PlaylistEditor();
|
||||
|
||||
virtual void switchTo() override;
|
||||
virtual void resize() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::PlaylistEditor; }
|
||||
|
||||
virtual void refresh() override;
|
||||
virtual void update() override;
|
||||
|
||||
virtual int windowTimeout() override;
|
||||
|
||||
virtual void mouseButtonPressed(MEVENT me) override;
|
||||
|
||||
virtual bool isLockable() override { return true; }
|
||||
virtual bool isMergable() override { return true; }
|
||||
|
||||
// Searchable implementation
|
||||
virtual bool allowsSearching() override;
|
||||
virtual const std::string &searchConstraint() override;
|
||||
virtual void setSearchConstraint(const std::string &constraint) override;
|
||||
virtual void clearSearchConstraint() override;
|
||||
virtual bool search(SearchDirection direction, bool wrap, bool skip_current) override;
|
||||
|
||||
// Filterable implementation
|
||||
virtual bool allowsFiltering() 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;
|
||||
|
||||
// HasColumns implementation
|
||||
virtual bool previousColumnAvailable() override;
|
||||
virtual void previousColumn() override;
|
||||
|
||||
virtual bool nextColumnAvailable() override;
|
||||
virtual void nextColumn() override;
|
||||
|
||||
// private members
|
||||
void updateTimer();
|
||||
|
||||
void requestPlaylistsUpdate() { m_playlists_update_requested = true; }
|
||||
void requestContentsUpdate() { m_content_update_requested = true; }
|
||||
|
||||
void locatePlaylist(const MPD::Playlist &playlist);
|
||||
|
||||
NC::Menu<MPD::Playlist> Playlists;
|
||||
SongMenu Content;
|
||||
|
||||
private:
|
||||
bool m_playlists_update_requested;
|
||||
bool m_content_update_requested;
|
||||
|
||||
boost::posix_time::ptime m_timer;
|
||||
|
||||
const int m_window_timeout;
|
||||
const boost::posix_time::time_duration m_fetching_delay;
|
||||
|
||||
Regex::Filter<MPD::Playlist> m_playlists_search_predicate;
|
||||
Regex::Filter<MPD::Song> m_content_search_predicate;
|
||||
};
|
||||
|
||||
extern PlaylistEditor *myPlaylistEditor;
|
||||
|
||||
#endif // NCMPCPP_PLAYLIST_EDITOR_H
|
||||
|
||||
169
src/screens/screen.cpp
Normal file
169
src/screens/screen.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "global.h"
|
||||
#include "screens/screen.h"
|
||||
#include "settings.h"
|
||||
|
||||
using Global::myScreen;
|
||||
using Global::myLockedScreen;
|
||||
using Global::myInactiveScreen;
|
||||
|
||||
void drawSeparator(int x)
|
||||
{
|
||||
color_set(Config.main_color.pairNumber(), nullptr);
|
||||
mvvline(Global::MainStartY, x, 0, Global::MainHeight);
|
||||
standend();
|
||||
refresh();
|
||||
}
|
||||
|
||||
void genericMouseButtonPressed(NC::Window &w, MEVENT me)
|
||||
{
|
||||
if (me.bstate & BUTTON5_PRESSED)
|
||||
{
|
||||
if (Config.mouse_list_scroll_whole_page)
|
||||
w.scroll(NC::Scroll::PageDown);
|
||||
else
|
||||
for (size_t i = 0; i < Config.lines_scrolled; ++i)
|
||||
w.scroll(NC::Scroll::Down);
|
||||
}
|
||||
else if (me.bstate & BUTTON4_PRESSED)
|
||||
{
|
||||
if (Config.mouse_list_scroll_whole_page)
|
||||
w.scroll(NC::Scroll::PageUp);
|
||||
else
|
||||
for (size_t i = 0; i < Config.lines_scrolled; ++i)
|
||||
w.scroll(NC::Scroll::Up);
|
||||
}
|
||||
}
|
||||
|
||||
void scrollpadMouseButtonPressed(NC::Scrollpad &w, MEVENT me)
|
||||
{
|
||||
if (me.bstate & BUTTON5_PRESSED)
|
||||
{
|
||||
for (size_t i = 0; i < Config.lines_scrolled; ++i)
|
||||
w.scroll(NC::Scroll::Down);
|
||||
}
|
||||
else if (me.bstate & BUTTON4_PRESSED)
|
||||
{
|
||||
for (size_t i = 0; i < Config.lines_scrolled; ++i)
|
||||
w.scroll(NC::Scroll::Up);
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
void BaseScreen::getWindowResizeParams(size_t &x_offset, size_t &width, bool adjust_locked_screen)
|
||||
{
|
||||
width = COLS;
|
||||
x_offset = 0;
|
||||
if (myLockedScreen && myInactiveScreen)
|
||||
{
|
||||
size_t locked_width = COLS*Config.locked_screen_width_part;
|
||||
if (myLockedScreen == this)
|
||||
width = locked_width;
|
||||
else
|
||||
{
|
||||
width = COLS-locked_width-1;
|
||||
x_offset = locked_width+1;
|
||||
|
||||
if (adjust_locked_screen)
|
||||
{
|
||||
myLockedScreen->resize();
|
||||
myLockedScreen->refresh();
|
||||
drawSeparator(x_offset-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BaseScreen::lock()
|
||||
{
|
||||
assert(myLockedScreen == nullptr);
|
||||
if (isLockable())
|
||||
{
|
||||
myLockedScreen = this;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void BaseScreen::unlock()
|
||||
{
|
||||
if (myInactiveScreen && myInactiveScreen != myLockedScreen)
|
||||
myScreen = myInactiveScreen;
|
||||
if (myScreen != myLockedScreen)
|
||||
myLockedScreen->switchTo();
|
||||
myLockedScreen = 0;
|
||||
myInactiveScreen = 0;
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
void applyToVisibleWindows(std::function<void(BaseScreen *)> f)
|
||||
{
|
||||
if (myLockedScreen && myScreen->isMergable())
|
||||
{
|
||||
if (myScreen == myLockedScreen)
|
||||
{
|
||||
if (myInactiveScreen)
|
||||
f(myInactiveScreen);
|
||||
}
|
||||
else
|
||||
f(myLockedScreen);
|
||||
}
|
||||
f(myScreen);
|
||||
}
|
||||
|
||||
void updateInactiveScreen(BaseScreen *screen_to_be_set)
|
||||
{
|
||||
if (myInactiveScreen && myLockedScreen != myInactiveScreen && myLockedScreen == screen_to_be_set)
|
||||
{
|
||||
// if we're here, the following conditions are (or at least should be) met:
|
||||
// 1. screen is split (myInactiveScreen is not null)
|
||||
// 2. current screen (myScreen) is not splittable, ie. is stacked on top of split screens
|
||||
// 3. current screen was activated while master screen was active
|
||||
// 4. we are returning to master screen
|
||||
// in such case we want to keep slave screen visible, so we never set it to null
|
||||
// as in "else" case. we also need to refresh it and redraw separator between
|
||||
// them as stacked screen probably has overwritten part ot it.
|
||||
myInactiveScreen->refresh();
|
||||
drawSeparator(COLS*Config.locked_screen_width_part);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (myLockedScreen == screen_to_be_set)
|
||||
myInactiveScreen = 0;
|
||||
else
|
||||
myInactiveScreen = myLockedScreen;
|
||||
}
|
||||
}
|
||||
|
||||
bool isVisible(BaseScreen *screen)
|
||||
{
|
||||
assert(screen != 0);
|
||||
if (myLockedScreen && myScreen->isMergable())
|
||||
return screen == myScreen || screen == myInactiveScreen || screen == myLockedScreen;
|
||||
else
|
||||
return screen == myScreen;
|
||||
}
|
||||
225
src/screens/screen.h
Normal file
225
src/screens/screen.h
Normal file
@@ -0,0 +1,225 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_SCREEN_H
|
||||
#define NCMPCPP_SCREEN_H
|
||||
|
||||
#include "curses/menu.h"
|
||||
#include "curses/scrollpad.h"
|
||||
#include "screens/screen_type.h"
|
||||
|
||||
void drawSeparator(int x);
|
||||
void genericMouseButtonPressed(NC::Window &w, MEVENT me);
|
||||
void scrollpadMouseButtonPressed(NC::Scrollpad &w, MEVENT me);
|
||||
|
||||
/// An interface for various instantiations of Screen template class. Since C++ doesn't like
|
||||
/// comparison of two different instantiations of the same template class we need the most
|
||||
/// basic class to be non-template to allow it.
|
||||
struct BaseScreen
|
||||
{
|
||||
BaseScreen() : hasToBeResized(false) { }
|
||||
virtual ~BaseScreen() { }
|
||||
|
||||
/// @see Screen::isActiveWindow()
|
||||
virtual bool isActiveWindow(const NC::Window &w_) const = 0;
|
||||
|
||||
/// @see Screen::activeWindow()
|
||||
virtual NC::Window *activeWindow() = 0;
|
||||
virtual const NC::Window *activeWindow() const = 0;
|
||||
|
||||
/// @see Screen::refresh()
|
||||
virtual void refresh() = 0;
|
||||
|
||||
/// @see Screen::refreshWindow()
|
||||
virtual void refreshWindow() = 0;
|
||||
|
||||
/// @see Screen::scroll()
|
||||
virtual void scroll(NC::Scroll where) = 0;
|
||||
|
||||
/// Method used for switching to screen
|
||||
virtual void switchTo() = 0;
|
||||
|
||||
/// Method that should resize screen
|
||||
/// if requested by hasToBeResized
|
||||
virtual void resize() = 0;
|
||||
|
||||
/// @return ncurses timeout parameter for the screen
|
||||
virtual int windowTimeout() = 0;
|
||||
|
||||
/// @return title of the screen
|
||||
virtual std::wstring title() = 0;
|
||||
|
||||
/// @return type of the screen
|
||||
virtual ScreenType type() = 0;
|
||||
|
||||
/// If the screen contantly has to update itself
|
||||
/// somehow, it should be called by this function.
|
||||
virtual void update() = 0;
|
||||
|
||||
/// @see Screen::mouseButtonPressed()
|
||||
virtual void mouseButtonPressed(MEVENT me) = 0;
|
||||
|
||||
/// @return true if screen can be locked. Note that returning
|
||||
/// false here doesn't neccesarily mean that screen is also not
|
||||
/// mergable (eg. lyrics screen is not lockable since that wouldn't
|
||||
/// make much sense, but it's perfectly fine to merge it).
|
||||
virtual bool isLockable() = 0;
|
||||
|
||||
/// @return true if screen is mergable, ie. can be "proper" subwindow
|
||||
/// if one of the screens is locked. Screens that somehow resemble popups
|
||||
/// will want to return false here.
|
||||
virtual bool isMergable() = 0;
|
||||
|
||||
/// Locks current screen.
|
||||
/// @return true if lock was successful, false otherwise. Note that
|
||||
/// if there is already locked screen, it'll not overwrite it.
|
||||
bool lock();
|
||||
|
||||
/// Should be set to true each time screen needs resize
|
||||
bool hasToBeResized;
|
||||
|
||||
/// Unlocks a screen, ie. hides merged window (if there is one set).
|
||||
static void unlock();
|
||||
|
||||
const static int defaultWindowTimeout = 500;
|
||||
|
||||
protected:
|
||||
/// Gets X offset and width of current screen to be used eg. in resize() function.
|
||||
/// @param adjust_locked_screen indicates whether this function should
|
||||
/// automatically adjust locked screen's dimensions (if there is one set)
|
||||
/// if current screen is going to be subwindow.
|
||||
void getWindowResizeParams(size_t &x_offset, size_t &width, bool adjust_locked_screen = true);
|
||||
};
|
||||
|
||||
void applyToVisibleWindows(std::function<void(BaseScreen *)> f);
|
||||
void updateInactiveScreen(BaseScreen *screen_to_be_set);
|
||||
bool isVisible(BaseScreen *screen);
|
||||
|
||||
/// Class that all screens should derive from. It provides basic interface
|
||||
/// for the screen to be working properly and assumes that we didn't forget
|
||||
/// about anything vital.
|
||||
///
|
||||
template <typename WindowT> struct Screen : public BaseScreen
|
||||
{
|
||||
typedef WindowT WindowType;
|
||||
typedef typename std::add_lvalue_reference<
|
||||
WindowType
|
||||
>::type WindowReference;
|
||||
typedef typename std::add_lvalue_reference<
|
||||
typename std::add_const<WindowType>::type
|
||||
>::type ConstWindowReference;
|
||||
|
||||
private:
|
||||
template <bool IsPointer, typename Result, typename ConstResult>
|
||||
struct getObject {
|
||||
static Result &apply(WindowReference w) { return w; }
|
||||
static ConstResult &constApply(ConstWindowReference w) { return w; }
|
||||
};
|
||||
template <typename Result, typename ConstResult>
|
||||
struct getObject<true, Result, ConstResult> {
|
||||
static Result &apply(WindowType w) { return *w; }
|
||||
static ConstResult &constApply(const WindowType w) { return *w; }
|
||||
};
|
||||
|
||||
typedef getObject<
|
||||
std::is_pointer<WindowT>::value,
|
||||
typename std::remove_pointer<WindowT>::type,
|
||||
typename std::add_const<
|
||||
typename std::remove_pointer<WindowT>::type
|
||||
>::type
|
||||
> Accessor;
|
||||
|
||||
public:
|
||||
Screen() { }
|
||||
Screen(WindowT w_) : w(w_) { }
|
||||
|
||||
virtual ~Screen() { }
|
||||
|
||||
virtual bool isActiveWindow(const NC::Window &w_) const override {
|
||||
return &Accessor::constApply(w) == &w_;
|
||||
}
|
||||
|
||||
/// Since some screens contain more that one window
|
||||
/// it's useful to determine the one that is being
|
||||
/// active
|
||||
/// @return address to window object cast to void *
|
||||
virtual NC::Window *activeWindow() override {
|
||||
return &Accessor::apply(w);
|
||||
}
|
||||
virtual const NC::Window *activeWindow() const override {
|
||||
return &Accessor::constApply(w);
|
||||
}
|
||||
|
||||
/// Refreshes whole screen
|
||||
virtual void refresh() override {
|
||||
Accessor::apply(w).display();
|
||||
}
|
||||
|
||||
/// Refreshes active window of the screen
|
||||
virtual void refreshWindow() override {
|
||||
Accessor::apply(w).display();
|
||||
}
|
||||
|
||||
/// Scrolls the screen by given amount of lines and
|
||||
/// if fancy scrolling feature is disabled, enters the
|
||||
/// loop that holds main loop until user releases the key
|
||||
/// @param where indicates where one wants to scroll
|
||||
virtual void scroll(NC::Scroll where) override {
|
||||
Accessor::apply(w).scroll(where);
|
||||
}
|
||||
|
||||
/// @return timeout parameter used for the screen (in ms)
|
||||
/// @default defaultWindowTimeout
|
||||
virtual int windowTimeout() override {
|
||||
return defaultWindowTimeout;
|
||||
}
|
||||
|
||||
/// Invoked after there was one of mouse buttons pressed
|
||||
/// @param me struct that contains coords of where the click
|
||||
/// had its place and button actions
|
||||
virtual void mouseButtonPressed(MEVENT me) override {
|
||||
genericMouseButtonPressed(Accessor::apply(w), me);
|
||||
}
|
||||
|
||||
/// @return currently active window
|
||||
WindowReference main() {
|
||||
return w;
|
||||
}
|
||||
ConstWindowReference main() const {
|
||||
return w;
|
||||
}
|
||||
|
||||
protected:
|
||||
/// Template parameter that should indicate the main type
|
||||
/// of window used by the screen. What is more, it should
|
||||
/// always be assigned to the currently active window (if
|
||||
/// acreen contains more that one)
|
||||
WindowT w;
|
||||
};
|
||||
|
||||
/// Specialization for Screen<Scrollpad>::mouseButtonPressed that should
|
||||
/// not scroll whole page, but rather a few lines (the number of them is
|
||||
/// defined in the config)
|
||||
template <> inline void Screen<NC::Scrollpad>::mouseButtonPressed(MEVENT me) {
|
||||
scrollpadMouseButtonPressed(w, me);
|
||||
}
|
||||
|
||||
#endif // NCMPCPP_SCREEN_H
|
||||
|
||||
60
src/screens/screen_switcher.h
Normal file
60
src/screens/screen_switcher.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_SCREEN_SWITCHER_H
|
||||
#define NCMPCPP_SCREEN_SWITCHER_H
|
||||
|
||||
#include "global.h"
|
||||
|
||||
class SwitchTo
|
||||
{
|
||||
template <bool ToBeExecuted, typename ScreenT> struct TabbableAction_ {
|
||||
static void execute(ScreenT *) { }
|
||||
};
|
||||
template <typename ScreenT> struct TabbableAction_<true, ScreenT> {
|
||||
static void execute(ScreenT *screen) {
|
||||
using Global::myScreen;
|
||||
// it has to work only if both current and previous screens are Tabbable
|
||||
if (dynamic_cast<Tabbable *>(myScreen))
|
||||
screen->setPreviousScreen(myScreen);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
template <typename ScreenT>
|
||||
static void execute(ScreenT *screen)
|
||||
{
|
||||
using Global::myScreen;
|
||||
using Global::myLockedScreen;
|
||||
|
||||
const bool isScreenMergable = screen->isMergable() && myLockedScreen;
|
||||
const bool isScreenTabbable = std::is_base_of<Tabbable, ScreenT>::value;
|
||||
|
||||
assert(myScreen != screen);
|
||||
if (isScreenMergable)
|
||||
updateInactiveScreen(screen);
|
||||
if (screen->hasToBeResized || isScreenMergable)
|
||||
screen->resize();
|
||||
TabbableAction_<isScreenTabbable, ScreenT>::execute(screen);
|
||||
myScreen = screen;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // NCMPCPP_SCREEN_SWITCHER_H
|
||||
203
src/screens/screen_type.cpp
Normal file
203
src/screens/screen_type.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "config.h"
|
||||
#include "screens/screen_type.h"
|
||||
|
||||
#include "screens/browser.h"
|
||||
#include "screens/clock.h"
|
||||
#include "screens/help.h"
|
||||
#include "screens/lastfm.h"
|
||||
#include "screens/lyrics.h"
|
||||
#include "screens/media_library.h"
|
||||
#include "screens/outputs.h"
|
||||
#include "screens/playlist.h"
|
||||
#include "screens/playlist_editor.h"
|
||||
#include "screens/search_engine.h"
|
||||
#include "screens/sel_items_adder.h"
|
||||
#include "screens/server_info.h"
|
||||
#include "screens/song_info.h"
|
||||
#include "screens/sort_playlist.h"
|
||||
#include "screens/tag_editor.h"
|
||||
#include "screens/tiny_tag_editor.h"
|
||||
#include "screens/visualizer.h"
|
||||
|
||||
std::string screenTypeToString(ScreenType st)
|
||||
{
|
||||
switch (st)
|
||||
{
|
||||
case ScreenType::Browser:
|
||||
return "browser";
|
||||
#ifdef ENABLE_CLOCK
|
||||
case ScreenType::Clock:
|
||||
return "clock";
|
||||
#endif // ENABLE_CLOCK
|
||||
case ScreenType::Help:
|
||||
return "help";
|
||||
case ScreenType::Lastfm:
|
||||
return "last_fm";
|
||||
case ScreenType::Lyrics:
|
||||
return "lyrics";
|
||||
case ScreenType::MediaLibrary:
|
||||
return "media_library";
|
||||
#ifdef ENABLE_OUTPUTS
|
||||
case ScreenType::Outputs:
|
||||
return "outputs";
|
||||
#endif // ENABLE_OUTPUTS
|
||||
case ScreenType::Playlist:
|
||||
return "playlist";
|
||||
case ScreenType::PlaylistEditor:
|
||||
return "playlist_editor";
|
||||
case ScreenType::SearchEngine:
|
||||
return "search_engine";
|
||||
case ScreenType::SelectedItemsAdder:
|
||||
return "selected_items_adder";
|
||||
case ScreenType::ServerInfo:
|
||||
return "server_info";
|
||||
case ScreenType::SongInfo:
|
||||
return "song_info";
|
||||
case ScreenType::SortPlaylistDialog:
|
||||
return "sort_playlist_dialog";
|
||||
#ifdef HAVE_TAGLIB_H
|
||||
case ScreenType::TagEditor:
|
||||
return "tag_editor";
|
||||
case ScreenType::TinyTagEditor:
|
||||
return "tiny_tag_editor";
|
||||
#endif // HAVE_TAGLIB_H
|
||||
case ScreenType::Unknown:
|
||||
return "unknown";
|
||||
#ifdef ENABLE_VISUALIZER
|
||||
case ScreenType::Visualizer:
|
||||
return "visualizer";
|
||||
#endif // ENABLE_VISUALIZER
|
||||
}
|
||||
// silence gcc warning
|
||||
throw std::runtime_error("unreachable");
|
||||
}
|
||||
|
||||
ScreenType stringtoStartupScreenType(const std::string &s)
|
||||
{
|
||||
ScreenType result = ScreenType::Unknown;
|
||||
if (s == "browser")
|
||||
result = ScreenType::Browser;
|
||||
# ifdef ENABLE_CLOCK
|
||||
else if (s == "clock")
|
||||
result = ScreenType::Clock;
|
||||
# endif // ENABLE_CLOCK
|
||||
else if (s == "help")
|
||||
result = ScreenType::Help;
|
||||
else if (s == "media_library")
|
||||
result = ScreenType::MediaLibrary;
|
||||
# ifdef ENABLE_OUTPUTS
|
||||
else if (s == "outputs")
|
||||
result = ScreenType::Outputs;
|
||||
# endif // ENABLE_OUTPUTS
|
||||
else if (s == "playlist")
|
||||
result = ScreenType::Playlist;
|
||||
else if (s == "playlist_editor")
|
||||
result = ScreenType::PlaylistEditor;
|
||||
else if (s == "search_engine")
|
||||
result = ScreenType::SearchEngine;
|
||||
# ifdef HAVE_TAGLIB_H
|
||||
else if (s == "tag_editor")
|
||||
result = ScreenType::TagEditor;
|
||||
# endif // HAVE_TAGLIB_H
|
||||
# ifdef ENABLE_VISUALIZER
|
||||
else if (s == "visualizer")
|
||||
result = ScreenType::Visualizer;
|
||||
# endif // ENABLE_VISUALIZER
|
||||
return result;
|
||||
}
|
||||
|
||||
ScreenType stringToScreenType(const std::string &s)
|
||||
{
|
||||
ScreenType result = stringtoStartupScreenType(s);
|
||||
if (result == ScreenType::Unknown)
|
||||
{
|
||||
if (s == "lyrics")
|
||||
result = ScreenType::Lyrics;
|
||||
else if (s == "last_fm")
|
||||
result = ScreenType::Lastfm;
|
||||
else if (s == "selected_items_adder")
|
||||
result = ScreenType::SelectedItemsAdder;
|
||||
else if (s == "server_info")
|
||||
result = ScreenType::ServerInfo;
|
||||
else if (s == "song_info")
|
||||
result = ScreenType::SongInfo;
|
||||
else if (s == "sort_playlist_dialog")
|
||||
result = ScreenType::SortPlaylistDialog;
|
||||
# ifdef HAVE_TAGLIB_H
|
||||
else if (s == "tiny_tag_editor")
|
||||
result = ScreenType::TinyTagEditor;
|
||||
# endif // HAVE_TAGLIB_H
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
BaseScreen *toScreen(ScreenType st)
|
||||
{
|
||||
switch (st)
|
||||
{
|
||||
case ScreenType::Browser:
|
||||
return myBrowser;
|
||||
# ifdef ENABLE_CLOCK
|
||||
case ScreenType::Clock:
|
||||
return myClock;
|
||||
# endif // ENABLE_CLOCK
|
||||
case ScreenType::Help:
|
||||
return myHelp;
|
||||
case ScreenType::Lastfm:
|
||||
return myLastfm;
|
||||
case ScreenType::Lyrics:
|
||||
return myLyrics;
|
||||
case ScreenType::MediaLibrary:
|
||||
return myLibrary;
|
||||
# ifdef ENABLE_OUTPUTS
|
||||
case ScreenType::Outputs:
|
||||
return myOutputs;
|
||||
# endif // ENABLE_OUTPUTS
|
||||
case ScreenType::Playlist:
|
||||
return myPlaylist;
|
||||
case ScreenType::PlaylistEditor:
|
||||
return myPlaylistEditor;
|
||||
case ScreenType::SearchEngine:
|
||||
return mySearcher;
|
||||
case ScreenType::SelectedItemsAdder:
|
||||
return mySelectedItemsAdder;
|
||||
case ScreenType::ServerInfo:
|
||||
return myServerInfo;
|
||||
case ScreenType::SongInfo:
|
||||
return mySongInfo;
|
||||
case ScreenType::SortPlaylistDialog:
|
||||
return mySortPlaylistDialog;
|
||||
# ifdef HAVE_TAGLIB_H
|
||||
case ScreenType::TagEditor:
|
||||
return myTagEditor;
|
||||
case ScreenType::TinyTagEditor:
|
||||
return myTinyTagEditor;
|
||||
# endif // HAVE_TAGLIB_H
|
||||
# ifdef ENABLE_VISUALIZER
|
||||
case ScreenType::Visualizer:
|
||||
return myVisualizer;
|
||||
# endif // ENABLE_VISUALIZER
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
66
src/screens/screen_type.h
Normal file
66
src/screens/screen_type.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_SCREEN_TYPE_H
|
||||
#define NCMPCPP_SCREEN_TYPE_H
|
||||
|
||||
#include <string>
|
||||
#include "config.h"
|
||||
|
||||
// forward declaration
|
||||
struct BaseScreen;
|
||||
|
||||
enum class ScreenType {
|
||||
Browser,
|
||||
# ifdef ENABLE_CLOCK
|
||||
Clock,
|
||||
# endif // ENABLE_CLOCK
|
||||
Help,
|
||||
Lastfm,
|
||||
Lyrics,
|
||||
MediaLibrary,
|
||||
# ifdef ENABLE_OUTPUTS
|
||||
Outputs,
|
||||
# endif // ENABLE_OUTPUTS
|
||||
Playlist,
|
||||
PlaylistEditor,
|
||||
SearchEngine,
|
||||
SelectedItemsAdder,
|
||||
ServerInfo,
|
||||
SongInfo,
|
||||
SortPlaylistDialog,
|
||||
# ifdef HAVE_TAGLIB_H
|
||||
TagEditor,
|
||||
TinyTagEditor,
|
||||
# endif // HAVE_TAGLIB_H
|
||||
Unknown,
|
||||
# ifdef ENABLE_VISUALIZER
|
||||
Visualizer,
|
||||
# endif // ENABLE_VISUALIZER
|
||||
};
|
||||
|
||||
std::string screenTypeToString(ScreenType st);
|
||||
|
||||
ScreenType stringtoStartupScreenType(const std::string &s);
|
||||
ScreenType stringToScreenType(const std::string &s);
|
||||
|
||||
BaseScreen *toScreen(ScreenType st);
|
||||
|
||||
#endif // NCMPCPP_SCREEN_TYPE_H
|
||||
615
src/screens/search_engine.cpp
Normal file
615
src/screens/search_engine.cpp
Normal file
@@ -0,0 +1,615 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include <array>
|
||||
#include <boost/range/detail/any_iterator.hpp>
|
||||
#include <iomanip>
|
||||
|
||||
#include "curses/menu_impl.h"
|
||||
#include "display.h"
|
||||
#include "global.h"
|
||||
#include "helpers.h"
|
||||
#include "screens/playlist.h"
|
||||
#include "screens/search_engine.h"
|
||||
#include "settings.h"
|
||||
#include "status.h"
|
||||
#include "statusbar.h"
|
||||
#include "helpers/song_iterator_maker.h"
|
||||
#include "utility/comparators.h"
|
||||
#include "title.h"
|
||||
#include "screens/screen_switcher.h"
|
||||
|
||||
using Global::MainHeight;
|
||||
using Global::MainStartY;
|
||||
|
||||
namespace ph = std::placeholders;
|
||||
|
||||
SearchEngine *mySearcher;
|
||||
|
||||
namespace {
|
||||
|
||||
/*const std::array<const std::string, 11> constraintsNames = {{
|
||||
"Any",
|
||||
"Artist",
|
||||
"Album Artist",
|
||||
"Title",
|
||||
"Album",
|
||||
"Filename",
|
||||
"Composer",
|
||||
"Performer",
|
||||
"Genre",
|
||||
"Date",
|
||||
"Comment"
|
||||
}};
|
||||
|
||||
const std::array<const char *, 3> searchModes = {{
|
||||
"Match if tag contains searched phrase (no regexes)",
|
||||
"Match if tag contains searched phrase (regexes supported)",
|
||||
"Match only if both values are the same"
|
||||
}};
|
||||
|
||||
namespace pos {
|
||||
const size_t searchIn = constraintsNames.size()-1+1+1; // separated
|
||||
const size_t searchMode = searchIn+1;
|
||||
const size_t search = searchMode+1+1; // separated
|
||||
const size_t reset = search+1;
|
||||
}*/
|
||||
|
||||
std::string SEItemToString(const SEItem &ei);
|
||||
bool SEItemEntryMatcher(const Regex::Regex &rx,
|
||||
const NC::Menu<SEItem>::Item &item,
|
||||
bool filter);
|
||||
|
||||
}
|
||||
|
||||
template <>
|
||||
struct SongPropertiesExtractor<SEItem>
|
||||
{
|
||||
template <typename ItemT>
|
||||
auto &operator()(ItemT &item) const
|
||||
{
|
||||
auto s = !item.isSeparator() && item.value().isSong()
|
||||
? &item.value().song()
|
||||
: nullptr;
|
||||
return m_cache.assign(&item.properties(), s);
|
||||
}
|
||||
|
||||
private:
|
||||
mutable SongProperties m_cache;
|
||||
};
|
||||
|
||||
SongIterator SearchEngineWindow::currentS()
|
||||
{
|
||||
return makeSongIterator(current());
|
||||
}
|
||||
|
||||
ConstSongIterator SearchEngineWindow::currentS() const
|
||||
{
|
||||
return makeConstSongIterator(current());
|
||||
}
|
||||
|
||||
SongIterator SearchEngineWindow::beginS()
|
||||
{
|
||||
return makeSongIterator(begin());
|
||||
}
|
||||
|
||||
ConstSongIterator SearchEngineWindow::beginS() const
|
||||
{
|
||||
return makeConstSongIterator(begin());
|
||||
}
|
||||
|
||||
SongIterator SearchEngineWindow::endS()
|
||||
{
|
||||
return makeSongIterator(end());
|
||||
}
|
||||
|
||||
ConstSongIterator SearchEngineWindow::endS() const
|
||||
{
|
||||
return makeConstSongIterator(end());
|
||||
}
|
||||
|
||||
std::vector<MPD::Song> SearchEngineWindow::getSelectedSongs()
|
||||
{
|
||||
std::vector<MPD::Song> result;
|
||||
for (auto &item : *this)
|
||||
{
|
||||
if (item.isSelected())
|
||||
{
|
||||
assert(item.value().isSong());
|
||||
result.push_back(item.value().song());
|
||||
}
|
||||
}
|
||||
// If no item is selected, add the current one if it's a song.
|
||||
if (result.empty() && !empty() && current()->value().isSong())
|
||||
result.push_back(current()->value().song());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
const char *SearchEngine::ConstraintsNames[] =
|
||||
{
|
||||
"Any",
|
||||
"Artist",
|
||||
"Album Artist",
|
||||
"Title",
|
||||
"Album",
|
||||
"Filename",
|
||||
"Composer",
|
||||
"Performer",
|
||||
"Genre",
|
||||
"Date",
|
||||
"Comment"
|
||||
};
|
||||
|
||||
const char *SearchEngine::SearchModes[] =
|
||||
{
|
||||
"Match if tag contains searched phrase (no regexes)",
|
||||
"Match if tag contains searched phrase (regexes supported)",
|
||||
"Match only if both values are the same",
|
||||
0
|
||||
};
|
||||
|
||||
size_t SearchEngine::StaticOptions = 20;
|
||||
size_t SearchEngine::ResetButton = 16;
|
||||
size_t SearchEngine::SearchButton = 15;
|
||||
|
||||
SearchEngine::SearchEngine()
|
||||
: Screen(NC::Menu<SEItem>(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border()))
|
||||
{
|
||||
w.setHighlightColor(Config.main_highlight_color);
|
||||
w.cyclicScrolling(Config.use_cyclic_scrolling);
|
||||
w.centeredCursor(Config.centered_cursor);
|
||||
w.setItemDisplayer(std::bind(Display::SEItems, ph::_1, std::cref(w)));
|
||||
w.setSelectedPrefix(Config.selected_item_prefix);
|
||||
w.setSelectedSuffix(Config.selected_item_suffix);
|
||||
SearchMode = &SearchModes[Config.search_engine_default_search_mode];
|
||||
}
|
||||
|
||||
void SearchEngine::resize()
|
||||
{
|
||||
size_t x_offset, width;
|
||||
getWindowResizeParams(x_offset, width);
|
||||
w.resize(width, MainHeight);
|
||||
w.moveTo(x_offset, MainStartY);
|
||||
switch (Config.search_engine_display_mode)
|
||||
{
|
||||
case DisplayMode::Columns:
|
||||
if (Config.titles_visibility)
|
||||
{
|
||||
w.setTitle(Display::Columns(w.getWidth()));
|
||||
break;
|
||||
}
|
||||
case DisplayMode::Classic:
|
||||
w.setTitle("");
|
||||
}
|
||||
hasToBeResized = 0;
|
||||
}
|
||||
|
||||
void SearchEngine::switchTo()
|
||||
{
|
||||
SwitchTo::execute(this);
|
||||
if (w.empty())
|
||||
Prepare();
|
||||
markSongsInPlaylist(w);
|
||||
drawHeader();
|
||||
}
|
||||
|
||||
std::wstring SearchEngine::title()
|
||||
{
|
||||
return L"Search engine";
|
||||
}
|
||||
|
||||
void SearchEngine::mouseButtonPressed(MEVENT me)
|
||||
{
|
||||
if (w.empty() || !w.hasCoords(me.x, me.y) || size_t(me.y) >= w.size())
|
||||
return;
|
||||
if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
|
||||
{
|
||||
if (!w.Goto(me.y))
|
||||
return;
|
||||
w.refresh();
|
||||
if ((me.bstate & BUTTON3_PRESSED)
|
||||
&& w.choice() < StaticOptions)
|
||||
runAction();
|
||||
else if (w.choice() >= StaticOptions)
|
||||
{
|
||||
bool play = me.bstate & BUTTON3_PRESSED;
|
||||
addItemToPlaylist(play);
|
||||
}
|
||||
}
|
||||
else
|
||||
Screen<WindowType>::mouseButtonPressed(me);
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool SearchEngine::allowsSearching()
|
||||
{
|
||||
return w.rbegin()->value().isSong();
|
||||
}
|
||||
|
||||
const std::string &SearchEngine::searchConstraint()
|
||||
{
|
||||
return m_search_predicate.constraint();
|
||||
}
|
||||
|
||||
void SearchEngine::setSearchConstraint(const std::string &constraint)
|
||||
{
|
||||
m_search_predicate = Regex::ItemFilter<SEItem>(
|
||||
constraint,
|
||||
Config.regex_type,
|
||||
std::bind(SEItemEntryMatcher, ph::_1, ph::_2, false));
|
||||
}
|
||||
|
||||
void SearchEngine::clearSearchConstraint()
|
||||
{
|
||||
m_search_predicate.clear();
|
||||
}
|
||||
|
||||
bool SearchEngine::search(SearchDirection direction, bool wrap, bool skip_current)
|
||||
{
|
||||
return ::search(w, m_search_predicate, direction, wrap, skip_current);
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool SearchEngine::allowsFiltering()
|
||||
{
|
||||
return allowsSearching();
|
||||
}
|
||||
|
||||
std::string SearchEngine::currentFilter()
|
||||
{
|
||||
std::string result;
|
||||
if (auto pred = w.filterPredicate<Regex::ItemFilter<SEItem>>())
|
||||
result = pred->constraint();
|
||||
return result;
|
||||
}
|
||||
|
||||
void SearchEngine::applyFilter(const std::string &constraint)
|
||||
{
|
||||
if (!constraint.empty())
|
||||
{
|
||||
w.applyFilter(Regex::ItemFilter<SEItem>(
|
||||
constraint,
|
||||
Config.regex_type,
|
||||
std::bind(SEItemEntryMatcher, ph::_1, ph::_2, true)));
|
||||
}
|
||||
else
|
||||
w.clearFilter();
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool SearchEngine::actionRunnable()
|
||||
{
|
||||
return !w.empty() && !w.current()->value().isSong();
|
||||
}
|
||||
|
||||
void SearchEngine::runAction()
|
||||
{
|
||||
size_t option = w.choice();
|
||||
if (option > ConstraintsNumber && option < SearchButton)
|
||||
w.current()->value().buffer().clear();
|
||||
|
||||
if (option < ConstraintsNumber)
|
||||
{
|
||||
Statusbar::ScopedLock slock;
|
||||
std::string constraint = ConstraintsNames[option];
|
||||
Statusbar::put() << NC::Format::Bold << constraint << NC::Format::NoBold << ": ";
|
||||
itsConstraints[option] = Global::wFooter->prompt(itsConstraints[option]);
|
||||
w.current()->value().buffer().clear();
|
||||
constraint.resize(13, ' ');
|
||||
w.current()->value().buffer() << NC::Format::Bold << constraint << NC::Format::NoBold << ": ";
|
||||
ShowTag(w.current()->value().buffer(), itsConstraints[option]);
|
||||
}
|
||||
else if (option == ConstraintsNumber+1)
|
||||
{
|
||||
Config.search_in_db = !Config.search_in_db;
|
||||
w.current()->value().buffer() << NC::Format::Bold << "Search in:" << NC::Format::NoBold << ' ' << (Config.search_in_db ? "Database" : "Current playlist");
|
||||
}
|
||||
else if (option == ConstraintsNumber+2)
|
||||
{
|
||||
if (!*++SearchMode)
|
||||
SearchMode = &SearchModes[0];
|
||||
w.current()->value().buffer() << NC::Format::Bold << "Search mode:" << NC::Format::NoBold << ' ' << *SearchMode;
|
||||
}
|
||||
else if (option == SearchButton)
|
||||
{
|
||||
w.clearFilter();
|
||||
Statusbar::print("Searching...");
|
||||
if (w.size() > StaticOptions)
|
||||
Prepare();
|
||||
Search();
|
||||
if (w.rbegin()->value().isSong())
|
||||
{
|
||||
if (Config.search_engine_display_mode == DisplayMode::Columns)
|
||||
w.setTitle(Config.titles_visibility ? Display::Columns(w.getWidth()) : "");
|
||||
size_t found = w.size()-SearchEngine::StaticOptions;
|
||||
found += 3; // don't count options inserted below
|
||||
w.insertSeparator(ResetButton+1);
|
||||
w.insertItem(ResetButton+2, SEItem(), NC::List::Properties::Bold | NC::List::Properties::Inactive);
|
||||
w.at(ResetButton+2).value().mkBuffer() << Config.color1 << "Search results: " << Config.color2 << "Found " << found << (found > 1 ? " songs" : " song") << NC::Color::Default;
|
||||
w.insertSeparator(ResetButton+3);
|
||||
markSongsInPlaylist(w);
|
||||
Statusbar::print("Searching finished");
|
||||
if (Config.block_search_constraints_change)
|
||||
for (size_t i = 0; i < StaticOptions-4; ++i)
|
||||
w.at(i).setInactive(true);
|
||||
w.scroll(NC::Scroll::Down);
|
||||
w.scroll(NC::Scroll::Down);
|
||||
}
|
||||
else
|
||||
Statusbar::print("No results found");
|
||||
}
|
||||
else if (option == ResetButton)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
else
|
||||
addSongToPlaylist(w.current()->value().song(), true);
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool SearchEngine::itemAvailable()
|
||||
{
|
||||
return !w.empty() && w.current()->value().isSong();
|
||||
}
|
||||
|
||||
bool SearchEngine::addItemToPlaylist(bool play)
|
||||
{
|
||||
return addSongToPlaylist(w.current()->value().song(), play);
|
||||
}
|
||||
|
||||
std::vector<MPD::Song> SearchEngine::getSelectedSongs()
|
||||
{
|
||||
return w.getSelectedSongs();
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
void SearchEngine::Prepare()
|
||||
{
|
||||
w.setTitle("");
|
||||
w.clear();
|
||||
w.resizeList(StaticOptions-3);
|
||||
|
||||
for (auto &item : w)
|
||||
item.setSelectable(false);
|
||||
|
||||
w.at(ConstraintsNumber).setSeparator(true);
|
||||
w.at(SearchButton-1).setSeparator(true);
|
||||
|
||||
for (size_t i = 0; i < ConstraintsNumber; ++i)
|
||||
{
|
||||
std::string constraint = ConstraintsNames[i];
|
||||
constraint.resize(13, ' ');
|
||||
w[i].value().mkBuffer() << NC::Format::Bold << constraint << NC::Format::NoBold << ": ";
|
||||
ShowTag(w[i].value().buffer(), itsConstraints[i]);
|
||||
}
|
||||
|
||||
w.at(ConstraintsNumber+1).value().mkBuffer() << NC::Format::Bold << "Search in:" << NC::Format::NoBold << ' ' << (Config.search_in_db ? "Database" : "Current playlist");
|
||||
w.at(ConstraintsNumber+2).value().mkBuffer() << NC::Format::Bold << "Search mode:" << NC::Format::NoBold << ' ' << *SearchMode;
|
||||
|
||||
w.at(SearchButton).value().mkBuffer() << "Search";
|
||||
w.at(ResetButton).value().mkBuffer() << "Reset";
|
||||
}
|
||||
|
||||
void SearchEngine::reset()
|
||||
{
|
||||
for (size_t i = 0; i < ConstraintsNumber; ++i)
|
||||
itsConstraints[i].clear();
|
||||
w.reset();
|
||||
Prepare();
|
||||
Statusbar::print("Search state reset");
|
||||
}
|
||||
|
||||
void SearchEngine::Search()
|
||||
{
|
||||
bool constraints_empty = 1;
|
||||
for (size_t i = 0; i < ConstraintsNumber; ++i)
|
||||
{
|
||||
if (!itsConstraints[i].empty())
|
||||
{
|
||||
constraints_empty = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (constraints_empty)
|
||||
return;
|
||||
|
||||
if (Config.search_in_db && (SearchMode == &SearchModes[0] || SearchMode == &SearchModes[2])) // use built-in mpd searching
|
||||
{
|
||||
Mpd.StartSearch(SearchMode == &SearchModes[2]);
|
||||
if (!itsConstraints[0].empty())
|
||||
Mpd.AddSearchAny(itsConstraints[0]);
|
||||
if (!itsConstraints[1].empty())
|
||||
Mpd.AddSearch(MPD_TAG_ARTIST, itsConstraints[1]);
|
||||
if (!itsConstraints[2].empty())
|
||||
Mpd.AddSearch(MPD_TAG_ALBUM_ARTIST, itsConstraints[2]);
|
||||
if (!itsConstraints[3].empty())
|
||||
Mpd.AddSearch(MPD_TAG_TITLE, itsConstraints[3]);
|
||||
if (!itsConstraints[4].empty())
|
||||
Mpd.AddSearch(MPD_TAG_ALBUM, itsConstraints[4]);
|
||||
if (!itsConstraints[5].empty())
|
||||
Mpd.AddSearchURI(itsConstraints[5]);
|
||||
if (!itsConstraints[6].empty())
|
||||
Mpd.AddSearch(MPD_TAG_COMPOSER, itsConstraints[6]);
|
||||
if (!itsConstraints[7].empty())
|
||||
Mpd.AddSearch(MPD_TAG_PERFORMER, itsConstraints[7]);
|
||||
if (!itsConstraints[8].empty())
|
||||
Mpd.AddSearch(MPD_TAG_GENRE, itsConstraints[8]);
|
||||
if (!itsConstraints[9].empty())
|
||||
Mpd.AddSearch(MPD_TAG_DATE, itsConstraints[9]);
|
||||
if (!itsConstraints[10].empty())
|
||||
Mpd.AddSearch(MPD_TAG_COMMENT, itsConstraints[10]);
|
||||
for (MPD::SongIterator s = Mpd.CommitSearchSongs(), end; s != end; ++s)
|
||||
w.addItem(std::move(*s));
|
||||
return;
|
||||
}
|
||||
|
||||
Regex::Regex rx[ConstraintsNumber];
|
||||
if (SearchMode != &SearchModes[2]) // match to pattern
|
||||
{
|
||||
for (size_t i = 0; i < ConstraintsNumber; ++i)
|
||||
{
|
||||
if (!itsConstraints[i].empty())
|
||||
{
|
||||
try
|
||||
{
|
||||
rx[i] = Regex::make(itsConstraints[i], Config.regex_type);
|
||||
}
|
||||
catch (boost::bad_expression &) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef boost::range_detail::any_iterator<
|
||||
const MPD::Song,
|
||||
boost::single_pass_traversal_tag,
|
||||
const MPD::Song &,
|
||||
std::ptrdiff_t
|
||||
> input_song_iterator;
|
||||
input_song_iterator s, end;
|
||||
if (Config.search_in_db)
|
||||
{
|
||||
s = input_song_iterator(getDatabaseIterator(Mpd));
|
||||
end = input_song_iterator(MPD::SongIterator());
|
||||
}
|
||||
else
|
||||
{
|
||||
s = input_song_iterator(myPlaylist->main().beginV());
|
||||
end = input_song_iterator(myPlaylist->main().endV());
|
||||
}
|
||||
|
||||
LocaleStringComparison cmp(std::locale(), Config.ignore_leading_the);
|
||||
for (; s != end; ++s)
|
||||
{
|
||||
bool any_found = true, found = true;
|
||||
|
||||
if (SearchMode != &SearchModes[2]) // match to pattern
|
||||
{
|
||||
if (!rx[0].empty())
|
||||
any_found =
|
||||
Regex::search(s->getArtist(), rx[0])
|
||||
|| Regex::search(s->getAlbumArtist(), rx[0])
|
||||
|| Regex::search(s->getTitle(), rx[0])
|
||||
|| Regex::search(s->getAlbum(), rx[0])
|
||||
|| Regex::search(s->getName(), rx[0])
|
||||
|| Regex::search(s->getComposer(), rx[0])
|
||||
|| Regex::search(s->getPerformer(), rx[0])
|
||||
|| Regex::search(s->getGenre(), rx[0])
|
||||
|| Regex::search(s->getDate(), rx[0])
|
||||
|| Regex::search(s->getComment(), rx[0]);
|
||||
if (found && !rx[1].empty())
|
||||
found = Regex::search(s->getArtist(), rx[1]);
|
||||
if (found && !rx[2].empty())
|
||||
found = Regex::search(s->getAlbumArtist(), rx[2]);
|
||||
if (found && !rx[3].empty())
|
||||
found = Regex::search(s->getTitle(), rx[3]);
|
||||
if (found && !rx[4].empty())
|
||||
found = Regex::search(s->getAlbum(), rx[4]);
|
||||
if (found && !rx[5].empty())
|
||||
found = Regex::search(s->getName(), rx[5]);
|
||||
if (found && !rx[6].empty())
|
||||
found = Regex::search(s->getComposer(), rx[6]);
|
||||
if (found && !rx[7].empty())
|
||||
found = Regex::search(s->getPerformer(), rx[7]);
|
||||
if (found && !rx[8].empty())
|
||||
found = Regex::search(s->getGenre(), rx[8]);
|
||||
if (found && !rx[9].empty())
|
||||
found = Regex::search(s->getDate(), rx[9]);
|
||||
if (found && !rx[10].empty())
|
||||
found = Regex::search(s->getComment(), rx[10]);
|
||||
}
|
||||
else // match only if values are equal
|
||||
{
|
||||
if (!itsConstraints[0].empty())
|
||||
any_found =
|
||||
!cmp(s->getArtist(), itsConstraints[0])
|
||||
|| !cmp(s->getAlbumArtist(), itsConstraints[0])
|
||||
|| !cmp(s->getTitle(), itsConstraints[0])
|
||||
|| !cmp(s->getAlbum(), itsConstraints[0])
|
||||
|| !cmp(s->getName(), itsConstraints[0])
|
||||
|| !cmp(s->getComposer(), itsConstraints[0])
|
||||
|| !cmp(s->getPerformer(), itsConstraints[0])
|
||||
|| !cmp(s->getGenre(), itsConstraints[0])
|
||||
|| !cmp(s->getDate(), itsConstraints[0])
|
||||
|| !cmp(s->getComment(), itsConstraints[0]);
|
||||
|
||||
if (found && !itsConstraints[1].empty())
|
||||
found = !cmp(s->getArtist(), itsConstraints[1]);
|
||||
if (found && !itsConstraints[2].empty())
|
||||
found = !cmp(s->getAlbumArtist(), itsConstraints[2]);
|
||||
if (found && !itsConstraints[3].empty())
|
||||
found = !cmp(s->getTitle(), itsConstraints[3]);
|
||||
if (found && !itsConstraints[4].empty())
|
||||
found = !cmp(s->getAlbum(), itsConstraints[4]);
|
||||
if (found && !itsConstraints[5].empty())
|
||||
found = !cmp(s->getName(), itsConstraints[5]);
|
||||
if (found && !itsConstraints[6].empty())
|
||||
found = !cmp(s->getComposer(), itsConstraints[6]);
|
||||
if (found && !itsConstraints[7].empty())
|
||||
found = !cmp(s->getPerformer(), itsConstraints[7]);
|
||||
if (found && !itsConstraints[8].empty())
|
||||
found = !cmp(s->getGenre(), itsConstraints[8]);
|
||||
if (found && !itsConstraints[9].empty())
|
||||
found = !cmp(s->getDate(), itsConstraints[9]);
|
||||
if (found && !itsConstraints[10].empty())
|
||||
found = !cmp(s->getComment(), itsConstraints[10]);
|
||||
}
|
||||
|
||||
if (any_found && found)
|
||||
w.addItem(*s);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::string SEItemToString(const SEItem &ei)
|
||||
{
|
||||
std::string result;
|
||||
if (ei.isSong())
|
||||
{
|
||||
switch (Config.search_engine_display_mode)
|
||||
{
|
||||
case DisplayMode::Classic:
|
||||
result = Format::stringify<char>(Config.song_list_format, &ei.song());
|
||||
break;
|
||||
case DisplayMode::Columns:
|
||||
result = Format::stringify<char>(Config.song_columns_mode_format, &ei.song());
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
result = ei.buffer().str();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SEItemEntryMatcher(const Regex::Regex &rx, const NC::Menu<SEItem>::Item &item, bool filter)
|
||||
{
|
||||
if (item.isSeparator() || !item.value().isSong())
|
||||
return filter;
|
||||
return Regex::search(SEItemToString(item.value()), rx);
|
||||
}
|
||||
|
||||
}
|
||||
160
src/screens/search_engine.h
Normal file
160
src/screens/search_engine.h
Normal file
@@ -0,0 +1,160 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_SEARCH_ENGINE_H
|
||||
#define NCMPCPP_SEARCH_ENGINE_H
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "interfaces.h"
|
||||
#include "mpdpp.h"
|
||||
#include "regex_filter.h"
|
||||
#include "screens/screen.h"
|
||||
#include "song_list.h"
|
||||
|
||||
struct SEItem
|
||||
{
|
||||
SEItem() : m_is_song(false), m_buffer(0) { }
|
||||
SEItem(NC::Buffer *buf) : m_is_song(false), m_buffer(buf) { }
|
||||
SEItem(const MPD::Song &s) : m_is_song(true), m_song(s) { }
|
||||
SEItem(const SEItem &ei) { *this = ei; }
|
||||
~SEItem() {
|
||||
if (!m_is_song)
|
||||
delete m_buffer;
|
||||
}
|
||||
|
||||
NC::Buffer &mkBuffer() {
|
||||
assert(!m_is_song);
|
||||
delete m_buffer;
|
||||
m_buffer = new NC::Buffer();
|
||||
return *m_buffer;
|
||||
}
|
||||
|
||||
bool isSong() const { return m_is_song; }
|
||||
|
||||
NC::Buffer &buffer() { assert(!m_is_song && m_buffer); return *m_buffer; }
|
||||
MPD::Song &song() { assert(m_is_song); return m_song; }
|
||||
|
||||
const NC::Buffer &buffer() const { assert(!m_is_song && m_buffer); return *m_buffer; }
|
||||
const MPD::Song &song() const { assert(m_is_song); return m_song; }
|
||||
|
||||
SEItem &operator=(const SEItem &se) {
|
||||
if (this == &se)
|
||||
return *this;
|
||||
m_is_song = se.m_is_song;
|
||||
if (se.m_is_song)
|
||||
m_song = se.m_song;
|
||||
else if (se.m_buffer)
|
||||
m_buffer = new NC::Buffer(*se.m_buffer);
|
||||
else
|
||||
m_buffer = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_is_song;
|
||||
|
||||
NC::Buffer *m_buffer;
|
||||
MPD::Song m_song;
|
||||
};
|
||||
|
||||
struct SearchEngineWindow: NC::Menu<SEItem>, SongList
|
||||
{
|
||||
SearchEngineWindow() { }
|
||||
SearchEngineWindow(NC::Menu<SEItem> &&base)
|
||||
: NC::Menu<SEItem>(std::move(base)) { }
|
||||
|
||||
virtual SongIterator currentS() override;
|
||||
virtual ConstSongIterator currentS() const override;
|
||||
virtual SongIterator beginS() override;
|
||||
virtual ConstSongIterator beginS() const override;
|
||||
virtual SongIterator endS() override;
|
||||
virtual ConstSongIterator endS() const override;
|
||||
|
||||
virtual std::vector<MPD::Song> getSelectedSongs() override;
|
||||
};
|
||||
|
||||
struct SearchEngine: Screen<SearchEngineWindow>, Filterable, HasActions, HasSongs, Searchable, Tabbable
|
||||
{
|
||||
SearchEngine();
|
||||
|
||||
// Screen<SearchEngineWindow> implementation
|
||||
virtual void resize() override;
|
||||
virtual void switchTo() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::SearchEngine; }
|
||||
|
||||
virtual void update() override { }
|
||||
|
||||
virtual void mouseButtonPressed(MEVENT me) override;
|
||||
|
||||
virtual bool isLockable() override { return true; }
|
||||
virtual bool isMergable() override { return true; }
|
||||
|
||||
// Searchable implementation
|
||||
virtual bool allowsSearching() override;
|
||||
virtual const std::string &searchConstraint() override;
|
||||
virtual void setSearchConstraint(const std::string &constraint) override;
|
||||
virtual void clearSearchConstraint() override;
|
||||
virtual bool search(SearchDirection direction, bool wrap, bool skip_current) override;
|
||||
|
||||
// Filterable implementation
|
||||
virtual bool allowsFiltering() override;
|
||||
virtual std::string currentFilter() override;
|
||||
virtual void applyFilter(const std::string &filter) override;
|
||||
|
||||
// HasActions implementation
|
||||
virtual bool actionRunnable() override;
|
||||
virtual void runAction() override;
|
||||
|
||||
// HasSongs implementation
|
||||
virtual bool itemAvailable() override;
|
||||
virtual bool addItemToPlaylist(bool play) override;
|
||||
virtual std::vector<MPD::Song> getSelectedSongs() override;
|
||||
|
||||
// private members
|
||||
void reset();
|
||||
|
||||
static size_t StaticOptions;
|
||||
static size_t SearchButton;
|
||||
static size_t ResetButton;
|
||||
|
||||
private:
|
||||
void Prepare();
|
||||
void Search();
|
||||
|
||||
Regex::ItemFilter<SEItem> m_search_predicate;
|
||||
|
||||
const char **SearchMode;
|
||||
|
||||
static const char *SearchModes[];
|
||||
|
||||
static const size_t ConstraintsNumber = 11;
|
||||
static const char *ConstraintsNames[];
|
||||
std::string itsConstraints[ConstraintsNumber];
|
||||
|
||||
static bool MatchToPattern;
|
||||
};
|
||||
|
||||
extern SearchEngine *mySearcher;
|
||||
|
||||
#endif // NCMPCPP_SEARCH_ENGINE_H
|
||||
|
||||
359
src/screens/sel_items_adder.cpp
Normal file
359
src/screens/sel_items_adder.cpp
Normal file
@@ -0,0 +1,359 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "curses/menu_impl.h"
|
||||
#include "screens/browser.h"
|
||||
#include "global.h"
|
||||
#include "helpers.h"
|
||||
#include "mpdpp.h"
|
||||
#include "screens/playlist.h"
|
||||
#include "screens/sel_items_adder.h"
|
||||
#include "settings.h"
|
||||
#include "statusbar.h"
|
||||
#include "utility/comparators.h"
|
||||
#include "screens/screen_switcher.h"
|
||||
#include "charset.h"
|
||||
|
||||
SelectedItemsAdder *mySelectedItemsAdder;
|
||||
|
||||
namespace {
|
||||
|
||||
void DisplayComponent(SelectedItemsAdder::Component &menu)
|
||||
{
|
||||
menu << Charset::utf8ToLocale(menu.drawn()->value().item());
|
||||
}
|
||||
|
||||
bool EntryMatcher(const Regex::Regex &rx, const NC::Menu<SelectedItemsAdder::Entry>::Item &item)
|
||||
{
|
||||
if (!item.isSeparator())
|
||||
return Regex::search(item.value().item(), rx);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SelectedItemsAdder::SelectedItemsAdder()
|
||||
{
|
||||
using Global::MainHeight;
|
||||
using Global::MainStartY;
|
||||
setDimensions();
|
||||
|
||||
m_playlist_selector = Component(
|
||||
(COLS-m_playlist_selector_width)/2,
|
||||
MainStartY+(MainHeight-m_playlist_selector_height)/2,
|
||||
m_playlist_selector_width,
|
||||
m_playlist_selector_height,
|
||||
"Add selected item(s) to...",
|
||||
Config.main_color,
|
||||
Config.window_border
|
||||
);
|
||||
m_playlist_selector.cyclicScrolling(Config.use_cyclic_scrolling);
|
||||
m_playlist_selector.centeredCursor(Config.centered_cursor);
|
||||
m_playlist_selector.setHighlightColor(Config.main_highlight_color);
|
||||
m_playlist_selector.setItemDisplayer(DisplayComponent);
|
||||
|
||||
m_position_selector = Component(
|
||||
(COLS-m_position_selector_width)/2,
|
||||
MainStartY+(MainHeight-m_position_selector_height)/2,
|
||||
m_position_selector_width,
|
||||
m_position_selector_height,
|
||||
"Where?",
|
||||
Config.main_color,
|
||||
Config.window_border
|
||||
);
|
||||
m_position_selector.cyclicScrolling(Config.use_cyclic_scrolling);
|
||||
m_position_selector.centeredCursor(Config.centered_cursor);
|
||||
m_position_selector.setHighlightColor(Config.main_highlight_color);
|
||||
m_position_selector.setItemDisplayer(DisplayComponent);
|
||||
|
||||
m_position_selector.addItem(Entry("At the end of playlist",
|
||||
std::bind(&Self::addAtTheEndOfPlaylist, this)
|
||||
));
|
||||
m_position_selector.addItem(Entry("At the beginning of playlist",
|
||||
std::bind(&Self::addAtTheBeginningOfPlaylist, this)
|
||||
));
|
||||
m_position_selector.addItem(Entry("After current song",
|
||||
std::bind(&Self::addAfterCurrentSong, this)
|
||||
));
|
||||
m_position_selector.addItem(Entry("After current album",
|
||||
std::bind(&Self::addAfterCurrentAlbum, this)
|
||||
));
|
||||
m_position_selector.addItem(Entry("After highlighted item",
|
||||
std::bind(&Self::addAfterHighlightedSong, this)
|
||||
));
|
||||
m_position_selector.addSeparator();
|
||||
m_position_selector.addItem(Entry("Cancel",
|
||||
std::bind(&Self::cancel, this)
|
||||
));
|
||||
|
||||
w = &m_playlist_selector;
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::switchTo()
|
||||
{
|
||||
using Global::myScreen;
|
||||
|
||||
auto hs = dynamic_cast<HasSongs *>(myScreen);
|
||||
if (!hs)
|
||||
return;
|
||||
|
||||
Statusbar::print(1, "Fetching selected songs...");
|
||||
m_selected_items = hs->getSelectedSongs();
|
||||
if (m_selected_items.empty())
|
||||
{
|
||||
Statusbar::print("No selected songs");
|
||||
return;
|
||||
}
|
||||
populatePlaylistSelector(myScreen);
|
||||
SwitchTo::execute(this);
|
||||
// default to main window
|
||||
w = &m_playlist_selector;
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::resize()
|
||||
{
|
||||
using Global::MainHeight;
|
||||
using Global::MainStartY;
|
||||
setDimensions();
|
||||
m_playlist_selector.resize(m_playlist_selector_width, m_playlist_selector_height);
|
||||
m_playlist_selector.moveTo(
|
||||
(COLS-m_playlist_selector_width)/2,
|
||||
MainStartY+(MainHeight-m_playlist_selector_height)/2
|
||||
);
|
||||
m_position_selector.resize(m_position_selector_width, m_position_selector_height);
|
||||
m_position_selector.moveTo(
|
||||
(COLS-m_position_selector_width)/2,
|
||||
MainStartY+(MainHeight-m_position_selector_height)/2
|
||||
);
|
||||
if (previousScreen() && previousScreen()->hasToBeResized) // resize background window
|
||||
{
|
||||
previousScreen()->resize();
|
||||
previousScreen()->refresh();
|
||||
}
|
||||
hasToBeResized = 0;
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::refresh()
|
||||
{
|
||||
if (isActiveWindow(m_position_selector))
|
||||
{
|
||||
m_playlist_selector.display();
|
||||
m_position_selector.display();
|
||||
}
|
||||
else if (isActiveWindow(m_playlist_selector))
|
||||
m_playlist_selector.display();
|
||||
}
|
||||
|
||||
std::wstring SelectedItemsAdder::title()
|
||||
{
|
||||
return previousScreen()->title();
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::mouseButtonPressed(MEVENT me)
|
||||
{
|
||||
if (w->empty() || !w->hasCoords(me.x, me.y) || size_t(me.y) >= w->size())
|
||||
return;
|
||||
if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
|
||||
{
|
||||
w->Goto(me.y);
|
||||
if (me.bstate & BUTTON3_PRESSED)
|
||||
runAction();
|
||||
}
|
||||
else
|
||||
Screen<WindowType>::mouseButtonPressed(me);
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool SelectedItemsAdder::actionRunnable()
|
||||
{
|
||||
return !w->empty();
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::runAction()
|
||||
{
|
||||
w->current()->value().run();
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
bool SelectedItemsAdder::allowsSearching()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string &SelectedItemsAdder::searchConstraint()
|
||||
{
|
||||
return m_search_predicate.constraint();
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::setSearchConstraint(const std::string &constraint)
|
||||
{
|
||||
m_search_predicate = Regex::ItemFilter<Entry>(
|
||||
constraint,
|
||||
Config.regex_type,
|
||||
EntryMatcher);
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::clearSearchConstraint()
|
||||
{
|
||||
m_search_predicate.clear();
|
||||
}
|
||||
|
||||
bool SelectedItemsAdder::search(SearchDirection direction, bool wrap, bool skip_current)
|
||||
{
|
||||
return ::search(*w, m_search_predicate, direction, wrap, skip_current);
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
void SelectedItemsAdder::populatePlaylistSelector(BaseScreen *old_screen)
|
||||
{
|
||||
// stored playlists don't support songs from outside of mpd database
|
||||
bool in_local_browser = old_screen == myBrowser && myBrowser->isLocal();
|
||||
|
||||
m_playlist_selector.reset();
|
||||
m_playlist_selector.clear();
|
||||
m_playlist_selector.addItem(Entry("Current playlist",
|
||||
std::bind(&Self::addToCurrentPlaylist, this)
|
||||
));
|
||||
if (!in_local_browser)
|
||||
{
|
||||
m_playlist_selector.addItem(Entry("New playlist",
|
||||
std::bind(&Self::addToNewPlaylist, this)
|
||||
));
|
||||
}
|
||||
m_playlist_selector.addSeparator();
|
||||
if (!in_local_browser)
|
||||
{
|
||||
size_t begin = m_playlist_selector.size();
|
||||
for (MPD::PlaylistIterator it = Mpd.GetPlaylists(), end; it != end; ++it)
|
||||
{
|
||||
m_playlist_selector.addItem(Entry(it->path(),
|
||||
std::bind(&Self::addToExistingPlaylist, this, it->path())
|
||||
));
|
||||
};
|
||||
std::sort(m_playlist_selector.beginV()+begin, m_playlist_selector.endV(),
|
||||
LocaleBasedSorting(std::locale(), Config.ignore_leading_the));
|
||||
if (begin < m_playlist_selector.size())
|
||||
m_playlist_selector.addSeparator();
|
||||
}
|
||||
m_playlist_selector.addItem(Entry("Cancel",
|
||||
std::bind(&Self::cancel, this)
|
||||
));
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::addToCurrentPlaylist()
|
||||
{
|
||||
w = &m_position_selector;
|
||||
m_position_selector.reset();
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::addToNewPlaylist() const
|
||||
{
|
||||
std::string playlist;
|
||||
{
|
||||
Statusbar::ScopedLock slock;
|
||||
Statusbar::put() << "Save playlist as: ";
|
||||
playlist = Global::wFooter->prompt();
|
||||
}
|
||||
addToExistingPlaylist(playlist);
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::addToExistingPlaylist(const std::string &playlist) const
|
||||
{
|
||||
Mpd.StartCommandsList();
|
||||
for (auto s = m_selected_items.begin(); s != m_selected_items.end(); ++s)
|
||||
Mpd.AddToPlaylist(playlist, *s);
|
||||
Mpd.CommitCommandsList();
|
||||
Statusbar::printf("Selected item(s) added to playlist \"%1%\"", playlist);
|
||||
switchToPreviousScreen();
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::addAtTheEndOfPlaylist() const
|
||||
{
|
||||
bool success = addSongsToPlaylist(m_selected_items.begin(), m_selected_items.end(), false, -1);
|
||||
exitSuccessfully(success);
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::addAtTheBeginningOfPlaylist() const
|
||||
{
|
||||
bool success = addSongsToPlaylist(m_selected_items.begin(), m_selected_items.end(), false, 0);
|
||||
exitSuccessfully(success);
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::addAfterCurrentSong() const
|
||||
{
|
||||
if (Status::State::player() == MPD::psStop)
|
||||
return;
|
||||
size_t pos = Status::State::currentSongPosition();
|
||||
++pos;
|
||||
bool success = addSongsToPlaylist(m_selected_items.begin(), m_selected_items.end(), false, pos);
|
||||
exitSuccessfully(success);
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::addAfterCurrentAlbum() const
|
||||
{
|
||||
if (Status::State::player() == MPD::psStop)
|
||||
return;
|
||||
auto &pl = myPlaylist->main();
|
||||
size_t pos = Status::State::currentSongPosition();
|
||||
std::string album = pl[pos].value().getAlbum();
|
||||
while (pos < pl.size() && pl[pos].value().getAlbum() == album)
|
||||
++pos;
|
||||
bool success = addSongsToPlaylist(m_selected_items.begin(), m_selected_items.end(), false, pos);
|
||||
exitSuccessfully(success);
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::addAfterHighlightedSong() const
|
||||
{
|
||||
size_t pos = myPlaylist->main().current()->value().getPosition();
|
||||
++pos;
|
||||
bool success = addSongsToPlaylist(m_selected_items.begin(), m_selected_items.end(), false, pos);
|
||||
exitSuccessfully(success);
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::cancel()
|
||||
{
|
||||
if (isActiveWindow(m_playlist_selector))
|
||||
switchToPreviousScreen();
|
||||
else if (isActiveWindow(m_position_selector))
|
||||
w = &m_playlist_selector;
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::exitSuccessfully(bool success) const
|
||||
{
|
||||
Statusbar::printf("Selected items added%1%", withErrors(success));
|
||||
switchToPreviousScreen();
|
||||
}
|
||||
|
||||
void SelectedItemsAdder::setDimensions()
|
||||
{
|
||||
using Global::MainHeight;
|
||||
|
||||
m_playlist_selector_width = COLS*0.6;
|
||||
m_playlist_selector_height = std::min(MainHeight, size_t(LINES*0.66));
|
||||
|
||||
m_position_selector_width = std::min(size_t(35), size_t(COLS));
|
||||
m_position_selector_height = std::min(size_t(11), MainHeight);
|
||||
}
|
||||
96
src/screens/sel_items_adder.h
Normal file
96
src/screens/sel_items_adder.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_SEL_ITEMS_ADDER_H
|
||||
#define NCMPCPP_SEL_ITEMS_ADDER_H
|
||||
|
||||
#include "runnable_item.h"
|
||||
#include "interfaces.h"
|
||||
#include "regex_filter.h"
|
||||
#include "screens/screen.h"
|
||||
#include "song.h"
|
||||
|
||||
struct SelectedItemsAdder: Screen<NC::Menu<RunnableItem<std::string, void()>> *>, HasActions, Searchable, Tabbable
|
||||
{
|
||||
typedef SelectedItemsAdder Self;
|
||||
typedef typename std::remove_pointer<WindowType>::type Component;
|
||||
typedef Component::Item::Type Entry;
|
||||
|
||||
SelectedItemsAdder();
|
||||
|
||||
virtual void switchTo() override;
|
||||
virtual void resize() override;
|
||||
virtual void refresh() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::SelectedItemsAdder; }
|
||||
|
||||
virtual void update() override { }
|
||||
|
||||
virtual void mouseButtonPressed(MEVENT me) override;
|
||||
|
||||
virtual bool isLockable() override { return false; }
|
||||
virtual bool isMergable() override { return false; }
|
||||
|
||||
// HasActions implementation
|
||||
virtual bool actionRunnable() override;
|
||||
virtual void runAction() override;
|
||||
|
||||
// Searchable implementation
|
||||
virtual bool allowsSearching() override;
|
||||
virtual const std::string &searchConstraint() override;
|
||||
virtual void setSearchConstraint(const std::string &constraint) override;
|
||||
virtual void clearSearchConstraint() override;
|
||||
virtual bool search(SearchDirection direction, bool wrap, bool skip_current) override;
|
||||
|
||||
private:
|
||||
void populatePlaylistSelector(BaseScreen *screen);
|
||||
|
||||
void addToCurrentPlaylist();
|
||||
void addToNewPlaylist() const;
|
||||
void addToExistingPlaylist(const std::string &playlist) const;
|
||||
void addAtTheEndOfPlaylist() const;
|
||||
void addAtTheBeginningOfPlaylist() const;
|
||||
void addAfterCurrentSong() const;
|
||||
void addAfterCurrentAlbum() const;
|
||||
void addAfterHighlightedSong() const;
|
||||
void cancel();
|
||||
void exitSuccessfully(bool success) const;
|
||||
|
||||
void setDimensions();
|
||||
|
||||
size_t m_playlist_selector_width;
|
||||
size_t m_playlist_selector_height;
|
||||
|
||||
size_t m_position_selector_width;
|
||||
size_t m_position_selector_height;
|
||||
|
||||
Component m_playlist_selector;
|
||||
Component m_position_selector;
|
||||
|
||||
std::vector<MPD::Song> m_selected_items;
|
||||
|
||||
Regex::ItemFilter<Entry> m_search_predicate;
|
||||
};
|
||||
|
||||
extern SelectedItemsAdder *mySelectedItemsAdder;
|
||||
|
||||
#endif // NCMPCPP_SEL_ITEMS_ADDER_H
|
||||
|
||||
129
src/screens/server_info.cpp
Normal file
129
src/screens/server_info.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <iomanip>
|
||||
|
||||
#include "global.h"
|
||||
#include "helpers.h"
|
||||
#include "screens/server_info.h"
|
||||
#include "statusbar.h"
|
||||
#include "screens/screen_switcher.h"
|
||||
|
||||
using Global::MainHeight;
|
||||
using Global::MainStartY;
|
||||
|
||||
ServerInfo *myServerInfo;
|
||||
|
||||
ServerInfo::ServerInfo()
|
||||
: m_timer(boost::posix_time::from_time_t(0))
|
||||
{
|
||||
SetDimensions();
|
||||
w = NC::Scrollpad((COLS-m_width)/2, (MainHeight-m_height)/2+MainStartY, m_width, m_height, "MPD server info", Config.main_color, Config.window_border);
|
||||
}
|
||||
|
||||
void ServerInfo::switchTo()
|
||||
{
|
||||
using Global::myScreen;
|
||||
if (myScreen != this)
|
||||
{
|
||||
SwitchTo::execute(this);
|
||||
|
||||
m_url_handlers.clear();
|
||||
std::copy(
|
||||
std::make_move_iterator(Mpd.GetURLHandlers()),
|
||||
std::make_move_iterator(MPD::StringIterator()),
|
||||
std::back_inserter(m_url_handlers)
|
||||
);
|
||||
|
||||
m_tag_types.clear();
|
||||
std::copy(
|
||||
std::make_move_iterator(Mpd.GetTagTypes()),
|
||||
std::make_move_iterator(MPD::StringIterator()),
|
||||
std::back_inserter(m_tag_types)
|
||||
);
|
||||
}
|
||||
else
|
||||
switchToPreviousScreen();
|
||||
}
|
||||
|
||||
void ServerInfo::resize()
|
||||
{
|
||||
SetDimensions();
|
||||
w.resize(m_width, m_height);
|
||||
w.moveTo((COLS-m_width)/2, (MainHeight-m_height)/2+MainStartY);
|
||||
if (previousScreen() && previousScreen()->hasToBeResized) // resize background window
|
||||
{
|
||||
previousScreen()->resize();
|
||||
previousScreen()->refresh();
|
||||
}
|
||||
hasToBeResized = 0;
|
||||
}
|
||||
|
||||
std::wstring ServerInfo::title()
|
||||
{
|
||||
return previousScreen()->title();
|
||||
}
|
||||
|
||||
void ServerInfo::update()
|
||||
{
|
||||
if (Global::Timer - m_timer < boost::posix_time::seconds(1))
|
||||
return;
|
||||
m_timer = Global::Timer;
|
||||
|
||||
MPD::Statistics stats = Mpd.getStatistics();
|
||||
if (stats.empty())
|
||||
return;
|
||||
|
||||
w.clear();
|
||||
|
||||
w << NC::Format::Bold << "Version: " << NC::Format::NoBold << "0." << Mpd.Version() << ".*\n";
|
||||
w << NC::Format::Bold << "Uptime: " << NC::Format::NoBold;
|
||||
ShowTime(w, stats.uptime(), 1);
|
||||
w << '\n';
|
||||
w << NC::Format::Bold << "Time playing: " << NC::Format::NoBold << MPD::Song::ShowTime(stats.playTime()) << '\n';
|
||||
w << '\n';
|
||||
w << NC::Format::Bold << "Total playtime: " << NC::Format::NoBold;
|
||||
ShowTime(w, stats.dbPlayTime(), 1);
|
||||
w << '\n';
|
||||
w << NC::Format::Bold << "Artist names: " << NC::Format::NoBold << stats.artists() << '\n';
|
||||
w << NC::Format::Bold << "Album names: " << NC::Format::NoBold << stats.albums() << '\n';
|
||||
w << NC::Format::Bold << "Songs in database: " << NC::Format::NoBold << stats.songs() << '\n';
|
||||
w << '\n';
|
||||
w << NC::Format::Bold << "Last DB update: " << NC::Format::NoBold << Timestamp(stats.dbUpdateTime()) << '\n';
|
||||
w << '\n';
|
||||
w << NC::Format::Bold << "URL Handlers:" << NC::Format::NoBold;
|
||||
for (auto it = m_url_handlers.begin(); it != m_url_handlers.end(); ++it)
|
||||
w << (it != m_url_handlers.begin() ? ", " : " ") << *it;
|
||||
w << "\n\n";
|
||||
w << NC::Format::Bold << "Tag Types:" << NC::Format::NoBold;
|
||||
for (auto it = m_tag_types.begin(); it != m_tag_types.end(); ++it)
|
||||
w << (it != m_tag_types.begin() ? ", " : " ") << *it;
|
||||
|
||||
w.flush();
|
||||
w.refresh();
|
||||
}
|
||||
|
||||
void ServerInfo::SetDimensions()
|
||||
{
|
||||
m_width = COLS*0.6;
|
||||
m_height = std::min(size_t(LINES*0.7), MainHeight);
|
||||
}
|
||||
|
||||
60
src/screens/server_info.h
Normal file
60
src/screens/server_info.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_SERVER_INFO_H
|
||||
#define NCMPCPP_SERVER_INFO_H
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time_types.hpp>
|
||||
|
||||
#include "interfaces.h"
|
||||
#include "screens/screen.h"
|
||||
|
||||
struct ServerInfo: Screen<NC::Scrollpad>, Tabbable
|
||||
{
|
||||
ServerInfo();
|
||||
|
||||
// Screen<NC::Scrollpad> implementation
|
||||
virtual void switchTo() override;
|
||||
virtual void resize() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::ServerInfo; }
|
||||
|
||||
virtual void update() override;
|
||||
|
||||
virtual bool isLockable() override { return false; }
|
||||
virtual bool isMergable() override { return false; }
|
||||
|
||||
private:
|
||||
void SetDimensions();
|
||||
|
||||
boost::posix_time::ptime m_timer;
|
||||
|
||||
std::vector<std::string> m_url_handlers;
|
||||
std::vector<std::string> m_tag_types;
|
||||
|
||||
size_t m_width;
|
||||
size_t m_height;
|
||||
};
|
||||
|
||||
extern ServerInfo *myServerInfo;
|
||||
|
||||
#endif // NCMPCPP_SERVER_INFO_H
|
||||
|
||||
149
src/screens/song_info.cpp
Normal file
149
src/screens/song_info.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "global.h"
|
||||
#include "helpers.h"
|
||||
#include "screens/song_info.h"
|
||||
#include "screens/tag_editor.h"
|
||||
#include "tags.h"
|
||||
#include "title.h"
|
||||
#include "screens/screen_switcher.h"
|
||||
|
||||
#ifdef HAVE_TAGLIB_H
|
||||
# include "fileref.h"
|
||||
# include "tag.h"
|
||||
# include "boost/lexical_cast.hpp"
|
||||
#endif // HAVE_TAGLIB_H
|
||||
|
||||
using Global::MainHeight;
|
||||
using Global::MainStartY;
|
||||
|
||||
SongInfo *mySongInfo;
|
||||
|
||||
const SongInfo::Metadata SongInfo::Tags[] =
|
||||
{
|
||||
{ "Title", &MPD::Song::getTitle, &MPD::MutableSong::setTitle },
|
||||
{ "Artist", &MPD::Song::getArtist, &MPD::MutableSong::setArtist },
|
||||
{ "Album Artist", &MPD::Song::getAlbumArtist, &MPD::MutableSong::setAlbumArtist },
|
||||
{ "Album", &MPD::Song::getAlbum, &MPD::MutableSong::setAlbum },
|
||||
{ "Date", &MPD::Song::getDate, &MPD::MutableSong::setDate },
|
||||
{ "Track", &MPD::Song::getTrack, &MPD::MutableSong::setTrack },
|
||||
{ "Genre", &MPD::Song::getGenre, &MPD::MutableSong::setGenre },
|
||||
{ "Composer", &MPD::Song::getComposer, &MPD::MutableSong::setComposer },
|
||||
{ "Performer", &MPD::Song::getPerformer, &MPD::MutableSong::setPerformer },
|
||||
{ "Disc", &MPD::Song::getDisc, &MPD::MutableSong::setDisc },
|
||||
{ "Comment", &MPD::Song::getComment, &MPD::MutableSong::setComment },
|
||||
{ 0, 0, 0 }
|
||||
};
|
||||
|
||||
SongInfo::SongInfo()
|
||||
: Screen(NC::Scrollpad(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border()))
|
||||
{ }
|
||||
|
||||
void SongInfo::resize()
|
||||
{
|
||||
size_t x_offset, width;
|
||||
getWindowResizeParams(x_offset, width);
|
||||
w.resize(width, MainHeight);
|
||||
w.moveTo(x_offset, MainStartY);
|
||||
hasToBeResized = 0;
|
||||
}
|
||||
|
||||
std::wstring SongInfo::title()
|
||||
{
|
||||
return L"Song info";
|
||||
}
|
||||
|
||||
void SongInfo::switchTo()
|
||||
{
|
||||
using Global::myScreen;
|
||||
if (myScreen != this)
|
||||
{
|
||||
auto s = currentSong(myScreen);
|
||||
if (!s)
|
||||
return;
|
||||
SwitchTo::execute(this);
|
||||
w.clear();
|
||||
w.reset();
|
||||
PrepareSong(*s);
|
||||
w.flush();
|
||||
// redraw header after we're done with the file, since reading it from disk
|
||||
// takes a bit of time and having header updated before content of a window
|
||||
// is displayed doesn't look nice.
|
||||
drawHeader();
|
||||
}
|
||||
else
|
||||
switchToPreviousScreen();
|
||||
}
|
||||
|
||||
void SongInfo::PrepareSong(const MPD::Song &s)
|
||||
{
|
||||
w << NC::Format::Bold << Config.color1 << "Filename: " << NC::Format::NoBold << Config.color2 << s.getName() << '\n' << NC::Color::End;
|
||||
w << NC::Format::Bold << "Directory: " << NC::Format::NoBold << Config.color2;
|
||||
ShowTag(w, s.getDirectory());
|
||||
w << "\n\n" << NC::Color::End;
|
||||
w << NC::Format::Bold << "Length: " << NC::Format::NoBold << Config.color2 << s.getLength() << '\n' << NC::Color::End;
|
||||
# ifdef HAVE_TAGLIB_H
|
||||
if (!Config.mpd_music_dir.empty() && !s.isStream())
|
||||
{
|
||||
std::string path_to_file;
|
||||
if (s.isFromDatabase())
|
||||
path_to_file += Config.mpd_music_dir;
|
||||
path_to_file += s.getURI();
|
||||
TagLib::FileRef f(path_to_file.c_str());
|
||||
if (!f.isNull())
|
||||
{
|
||||
std::string channels;
|
||||
switch (f.audioProperties()->channels())
|
||||
{
|
||||
case 1:
|
||||
channels = "Mono";
|
||||
break;
|
||||
case 2:
|
||||
channels = "Stereo";
|
||||
break;
|
||||
default:
|
||||
channels = boost::lexical_cast<std::string>(f.audioProperties()->channels());
|
||||
break;
|
||||
}
|
||||
w << NC::Format::Bold << "Bitrate: " << NC::Format::NoBold << Config.color2 << f.audioProperties()->bitrate() << " kbps\n" << NC::Color::End;
|
||||
w << NC::Format::Bold << "Sample rate: " << NC::Format::NoBold << Config.color2 << f.audioProperties()->sampleRate() << " Hz\n" << NC::Color::End;
|
||||
w << NC::Format::Bold << "Channels: " << NC::Format::NoBold << Config.color2 << channels << NC::Color::End << "\n";
|
||||
|
||||
auto rginfo = Tags::readReplayGain(f.file());
|
||||
if (!rginfo.empty())
|
||||
{
|
||||
w << NC::Format::Bold << "\nReference loudness: " << NC::Format::NoBold << Config.color2 << rginfo.referenceLoudness() << NC::Color::End << "\n";
|
||||
w << NC::Format::Bold << "Track gain: " << NC::Format::NoBold << Config.color2 << rginfo.trackGain() << NC::Color::End << "\n";
|
||||
w << NC::Format::Bold << "Track peak: " << NC::Format::NoBold << Config.color2 << rginfo.trackPeak() << NC::Color::End << "\n";
|
||||
w << NC::Format::Bold << "Album gain: " << NC::Format::NoBold << Config.color2 << rginfo.albumGain() << NC::Color::End << "\n";
|
||||
w << NC::Format::Bold << "Album peak: " << NC::Format::NoBold << Config.color2 << rginfo.albumPeak() << NC::Color::End << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
# endif // HAVE_TAGLIB_H
|
||||
w << NC::Color::Default;
|
||||
|
||||
for (const Metadata *m = Tags; m->Name; ++m)
|
||||
{
|
||||
w << NC::Format::Bold << '\n' << m->Name << ": " << NC::Format::NoBold;
|
||||
ShowTag(w, s.getTags(m->Get));
|
||||
}
|
||||
}
|
||||
61
src/screens/song_info.h
Normal file
61
src/screens/song_info.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_SONG_INFO_H
|
||||
#define NCMPCPP_SONG_INFO_H
|
||||
|
||||
#include "interfaces.h"
|
||||
#include "mutable_song.h"
|
||||
#include "screens/screen.h"
|
||||
|
||||
struct SongInfo: Screen<NC::Scrollpad>, Tabbable
|
||||
{
|
||||
struct Metadata
|
||||
{
|
||||
const char *Name;
|
||||
MPD::Song::GetFunction Get;
|
||||
MPD::MutableSong::SetFunction Set;
|
||||
};
|
||||
|
||||
SongInfo();
|
||||
|
||||
// Screen<NC::Scrollpad> implementation
|
||||
virtual void switchTo() override;
|
||||
virtual void resize() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::SongInfo; }
|
||||
|
||||
virtual void update() override { }
|
||||
|
||||
virtual bool isLockable() override { return false; }
|
||||
virtual bool isMergable() override { return true; }
|
||||
|
||||
// private members
|
||||
static const Metadata Tags[];
|
||||
|
||||
private:
|
||||
void PrepareSong(const MPD::Song &s);
|
||||
};
|
||||
|
||||
extern SongInfo *mySongInfo;
|
||||
|
||||
#endif // NCMPCPP_SONG_INFO_H
|
||||
|
||||
232
src/screens/sort_playlist.cpp
Normal file
232
src/screens/sort_playlist.cpp
Normal file
@@ -0,0 +1,232 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "curses/menu_impl.h"
|
||||
#include "charset.h"
|
||||
#include "display.h"
|
||||
#include "global.h"
|
||||
#include "helpers.h"
|
||||
#include "screens/playlist.h"
|
||||
#include "settings.h"
|
||||
#include "screens/sort_playlist.h"
|
||||
#include "statusbar.h"
|
||||
#include "utility/comparators.h"
|
||||
#include "screens/screen_switcher.h"
|
||||
|
||||
SortPlaylistDialog *mySortPlaylistDialog;
|
||||
|
||||
SortPlaylistDialog::SortPlaylistDialog()
|
||||
{
|
||||
typedef WindowType::Item::Type Entry;
|
||||
|
||||
using Global::MainHeight;
|
||||
using Global::MainStartY;
|
||||
|
||||
setDimensions();
|
||||
w = WindowType((COLS-m_width)/2, (MainHeight-m_height)/2+MainStartY, m_width, m_height, "Sort songs by...", Config.main_color, Config.window_border);
|
||||
w.cyclicScrolling(Config.use_cyclic_scrolling);
|
||||
w.centeredCursor(Config.centered_cursor);
|
||||
w.setItemDisplayer([](Self::WindowType &menu) {
|
||||
menu << Charset::utf8ToLocale(menu.drawn()->value().item().first);
|
||||
});
|
||||
|
||||
w.addItem(Entry(std::make_pair("Artist", &MPD::Song::getArtist),
|
||||
std::bind(&Self::moveSortOrderHint, this)
|
||||
));
|
||||
w.addItem(Entry(std::make_pair("Album artist", &MPD::Song::getAlbumArtist),
|
||||
std::bind(&Self::moveSortOrderHint, this)
|
||||
));
|
||||
w.addItem(Entry(std::make_pair("Album", &MPD::Song::getAlbum),
|
||||
std::bind(&Self::moveSortOrderHint, this)
|
||||
));
|
||||
w.addItem(Entry(std::make_pair("Disc", &MPD::Song::getDisc),
|
||||
std::bind(&Self::moveSortOrderHint, this)
|
||||
));
|
||||
w.addItem(Entry(std::make_pair("Track", &MPD::Song::getTrack),
|
||||
std::bind(&Self::moveSortOrderHint, this)
|
||||
));
|
||||
w.addItem(Entry(std::make_pair("Genre", &MPD::Song::getGenre),
|
||||
std::bind(&Self::moveSortOrderHint, this)
|
||||
));
|
||||
w.addItem(Entry(std::make_pair("Date", &MPD::Song::getDate),
|
||||
std::bind(&Self::moveSortOrderHint, this)
|
||||
));
|
||||
w.addItem(Entry(std::make_pair("Composer", &MPD::Song::getComposer),
|
||||
std::bind(&Self::moveSortOrderHint, this)
|
||||
));
|
||||
w.addItem(Entry(std::make_pair("Performer", &MPD::Song::getPerformer),
|
||||
std::bind(&Self::moveSortOrderHint, this)
|
||||
));
|
||||
w.addItem(Entry(std::make_pair("Title", &MPD::Song::getTitle),
|
||||
std::bind(&Self::moveSortOrderHint, this)
|
||||
));
|
||||
w.addItem(Entry(std::make_pair("Filename", &MPD::Song::getURI),
|
||||
std::bind(&Self::moveSortOrderHint, this)
|
||||
));
|
||||
w.addSeparator();
|
||||
w.addItem(Entry(std::make_pair("Sort", static_cast<MPD::Song::GetFunction>(0)),
|
||||
std::bind(&Self::sort, this)
|
||||
));
|
||||
w.addItem(Entry(std::make_pair("Cancel", static_cast<MPD::Song::GetFunction>(0)),
|
||||
std::bind(&Self::cancel, this)
|
||||
));
|
||||
}
|
||||
|
||||
void SortPlaylistDialog::switchTo()
|
||||
{
|
||||
SwitchTo::execute(this);
|
||||
w.reset();
|
||||
}
|
||||
|
||||
void SortPlaylistDialog::resize()
|
||||
{
|
||||
using Global::MainHeight;
|
||||
using Global::MainStartY;
|
||||
setDimensions();
|
||||
w.resize(m_width, m_height);
|
||||
w.moveTo((COLS-m_width)/2, (MainHeight-m_height)/2+MainStartY);
|
||||
hasToBeResized = false;
|
||||
}
|
||||
|
||||
std::wstring SortPlaylistDialog::title()
|
||||
{
|
||||
return previousScreen()->title();
|
||||
}
|
||||
|
||||
void SortPlaylistDialog::mouseButtonPressed(MEVENT me)
|
||||
{
|
||||
if (w.hasCoords(me.x, me.y))
|
||||
{
|
||||
if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
|
||||
{
|
||||
w.Goto(me.y);
|
||||
if (me.bstate & BUTTON3_PRESSED)
|
||||
runAction();
|
||||
}
|
||||
else
|
||||
Screen<WindowType>::mouseButtonPressed(me);
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
bool SortPlaylistDialog::actionRunnable()
|
||||
{
|
||||
return !w.empty();
|
||||
}
|
||||
|
||||
void SortPlaylistDialog::runAction()
|
||||
{
|
||||
w.current()->value().run();
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
void SortPlaylistDialog::moveSortOrderDown()
|
||||
{
|
||||
auto cur = w.currentV();
|
||||
if ((cur+1)->item().second)
|
||||
{
|
||||
std::iter_swap(cur, cur+1);
|
||||
w.scroll(NC::Scroll::Down);
|
||||
}
|
||||
}
|
||||
|
||||
void SortPlaylistDialog::moveSortOrderUp()
|
||||
{
|
||||
auto cur = w.currentV();
|
||||
if (cur > w.beginV() && cur->item().second)
|
||||
{
|
||||
std::iter_swap(cur, cur-1);
|
||||
w.scroll(NC::Scroll::Up);
|
||||
}
|
||||
}
|
||||
|
||||
void SortPlaylistDialog::moveSortOrderHint() const
|
||||
{
|
||||
Statusbar::print("Move tag types up and down to adjust sort order");
|
||||
}
|
||||
|
||||
void SortPlaylistDialog::sort() const
|
||||
{
|
||||
auto &pl = myPlaylist->main();
|
||||
auto begin = pl.begin(), end = pl.end();
|
||||
if (!findSelectedRange(begin, end))
|
||||
return;
|
||||
|
||||
size_t start_pos = begin - pl.begin();
|
||||
std::vector<MPD::Song> playlist;
|
||||
playlist.reserve(end - begin);
|
||||
for (; begin != end; ++begin)
|
||||
playlist.push_back(begin->value());
|
||||
|
||||
typedef std::vector<MPD::Song>::iterator Iterator;
|
||||
LocaleStringComparison cmp(std::locale(), Config.ignore_leading_the);
|
||||
std::function<void(Iterator, Iterator)> iter_swap, quick_sort;
|
||||
auto song_cmp = [this, &cmp](const MPD::Song &a, const MPD::Song &b) -> bool {
|
||||
for (auto it = w.beginV(); it->item().second; ++it)
|
||||
{
|
||||
int res = cmp(a.getTags(it->item().second),
|
||||
b.getTags(it->item().second));
|
||||
if (res != 0)
|
||||
return res < 0;
|
||||
}
|
||||
return a.getPosition() < b.getPosition();
|
||||
};
|
||||
iter_swap = [&playlist, &start_pos](Iterator a, Iterator b) {
|
||||
std::iter_swap(a, b);
|
||||
Mpd.Swap(start_pos+a-playlist.begin(), start_pos+b-playlist.begin());
|
||||
};
|
||||
quick_sort = [this, &song_cmp, &quick_sort, &iter_swap](Iterator first, Iterator last) {
|
||||
if (last-first > 1)
|
||||
{
|
||||
Iterator pivot = first+Global::RNG()%(last-first);
|
||||
iter_swap(pivot, last-1);
|
||||
pivot = last-1;
|
||||
|
||||
Iterator tmp = first;
|
||||
for (Iterator i = first; i != pivot; ++i)
|
||||
if (song_cmp(*i, *pivot))
|
||||
iter_swap(i, tmp++);
|
||||
iter_swap(tmp, pivot);
|
||||
|
||||
quick_sort(first, tmp);
|
||||
quick_sort(tmp+1, last);
|
||||
}
|
||||
};
|
||||
|
||||
Statusbar::print("Sorting...");
|
||||
Mpd.StartCommandsList();
|
||||
quick_sort(playlist.begin(), playlist.end());
|
||||
Mpd.CommitCommandsList();
|
||||
Statusbar::print("Range sorted");
|
||||
switchToPreviousScreen();
|
||||
}
|
||||
|
||||
void SortPlaylistDialog::cancel() const
|
||||
{
|
||||
switchToPreviousScreen();
|
||||
}
|
||||
|
||||
void SortPlaylistDialog::setDimensions()
|
||||
{
|
||||
m_height = std::min(size_t(17), Global::MainHeight);
|
||||
m_width = 30;
|
||||
}
|
||||
70
src/screens/sort_playlist.h
Normal file
70
src/screens/sort_playlist.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_SORT_PLAYLIST_H
|
||||
#define NCMPCPP_SORT_PLAYLIST_H
|
||||
|
||||
#include "runnable_item.h"
|
||||
#include "interfaces.h"
|
||||
#include "screens/screen.h"
|
||||
#include "song.h"
|
||||
|
||||
struct SortPlaylistDialog
|
||||
: Screen<NC::Menu<RunnableItem<std::pair<std::string, MPD::Song::GetFunction>, void()>>>, HasActions, Tabbable
|
||||
{
|
||||
typedef SortPlaylistDialog Self;
|
||||
|
||||
SortPlaylistDialog();
|
||||
|
||||
virtual void switchTo() override;
|
||||
virtual void resize() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::SortPlaylistDialog; }
|
||||
|
||||
virtual void update() override { }
|
||||
|
||||
virtual void mouseButtonPressed(MEVENT me) override;
|
||||
|
||||
virtual bool isLockable() override { return false; }
|
||||
virtual bool isMergable() override { return false; }
|
||||
|
||||
// HasActions implementation
|
||||
virtual bool actionRunnable() override;
|
||||
virtual void runAction() override;
|
||||
|
||||
// private members
|
||||
void moveSortOrderUp();
|
||||
void moveSortOrderDown();
|
||||
|
||||
private:
|
||||
void moveSortOrderHint() const;
|
||||
void sort() const;
|
||||
void cancel() const;
|
||||
|
||||
void setDimensions();
|
||||
|
||||
size_t m_height;
|
||||
size_t m_width;
|
||||
};
|
||||
|
||||
extern SortPlaylistDialog *mySortPlaylistDialog;
|
||||
|
||||
#endif // NCMPCPP_SORT_PLAYLIST_H
|
||||
1195
src/screens/tag_editor.cpp
Normal file
1195
src/screens/tag_editor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
125
src/screens/tag_editor.h
Normal file
125
src/screens/tag_editor.h
Normal file
@@ -0,0 +1,125 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_TAG_EDITOR_H
|
||||
#define NCMPCPP_TAG_EDITOR_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_TAGLIB_H
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "interfaces.h"
|
||||
#include "mutable_song.h"
|
||||
#include "regex_filter.h"
|
||||
#include "screens/screen.h"
|
||||
#include "song_list.h"
|
||||
|
||||
struct TagsWindow: NC::Menu<MPD::MutableSong>, SongList
|
||||
{
|
||||
TagsWindow() { }
|
||||
TagsWindow(NC::Menu<MPD::MutableSong> &&base)
|
||||
: NC::Menu<MPD::MutableSong>(std::move(base)) { }
|
||||
|
||||
virtual SongIterator currentS() override;
|
||||
virtual ConstSongIterator currentS() const override;
|
||||
virtual SongIterator beginS() override;
|
||||
virtual ConstSongIterator beginS() const override;
|
||||
virtual SongIterator endS() override;
|
||||
virtual ConstSongIterator endS() const override;
|
||||
|
||||
virtual std::vector<MPD::Song> getSelectedSongs() override;
|
||||
};
|
||||
|
||||
struct TagEditor: Screen<NC::Window *>, HasActions, HasColumns, HasSongs, Searchable, Tabbable
|
||||
{
|
||||
TagEditor();
|
||||
|
||||
virtual void resize() override;
|
||||
virtual void switchTo() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::TagEditor; }
|
||||
|
||||
virtual void refresh() override;
|
||||
virtual void update() override;
|
||||
|
||||
virtual void mouseButtonPressed(MEVENT) override;
|
||||
|
||||
virtual bool isLockable() override { return true; }
|
||||
virtual bool isMergable() override { return true; }
|
||||
|
||||
// Searchable implementation
|
||||
virtual bool allowsSearching() override;
|
||||
virtual const std::string &searchConstraint() override;
|
||||
virtual void setSearchConstraint(const std::string &constraint) override;
|
||||
virtual void clearSearchConstraint() override;
|
||||
virtual bool search(SearchDirection direction, bool wrap, bool skip_current) override;
|
||||
|
||||
// HasActions implementation
|
||||
virtual bool actionRunnable() override;
|
||||
virtual void runAction() override;
|
||||
|
||||
// HasSongs implementation
|
||||
virtual bool itemAvailable() override;
|
||||
virtual bool addItemToPlaylist(bool play) override;
|
||||
virtual std::vector<MPD::Song> getSelectedSongs() override;
|
||||
|
||||
// HasColumns implementation
|
||||
virtual bool previousColumnAvailable() override;
|
||||
virtual void previousColumn() override;
|
||||
|
||||
virtual bool nextColumnAvailable() override;
|
||||
virtual void nextColumn() override;
|
||||
|
||||
// private members
|
||||
bool enterDirectory();
|
||||
void LocateSong(const MPD::Song &s);
|
||||
const std::string &CurrentDir() { return itsBrowsedDir; }
|
||||
|
||||
NC::Menu< std::pair<std::string, std::string> > *Dirs;
|
||||
NC::Menu<std::string> *TagTypes;
|
||||
TagsWindow *Tags;
|
||||
|
||||
private:
|
||||
void SetDimensions(size_t, size_t);
|
||||
|
||||
std::vector<MPD::MutableSong *> EditedSongs;
|
||||
NC::Menu<std::string> *FParserDialog;
|
||||
NC::Menu<std::string> *FParser;
|
||||
NC::Scrollpad *FParserHelper;
|
||||
NC::Scrollpad *FParserLegend;
|
||||
NC::Scrollpad *FParserPreview;
|
||||
bool FParserUsePreview;
|
||||
|
||||
std::string itsBrowsedDir;
|
||||
std::string itsHighlightedDir;
|
||||
|
||||
Regex::Filter<std::pair<std::string, std::string>> m_directories_search_predicate;
|
||||
Regex::Filter<MPD::MutableSong> m_songs_search_predicate;
|
||||
};
|
||||
|
||||
extern TagEditor *myTagEditor;
|
||||
|
||||
#endif // HAVE_TAGLIB_H
|
||||
|
||||
#endif // NCMPCPP_TAG_EDITOR_H
|
||||
|
||||
250
src/screens/tiny_tag_editor.cpp
Normal file
250
src/screens/tiny_tag_editor.cpp
Normal file
@@ -0,0 +1,250 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "screens/tiny_tag_editor.h"
|
||||
|
||||
#ifdef HAVE_TAGLIB_H
|
||||
|
||||
#include <boost/locale/conversion.hpp>
|
||||
|
||||
// taglib includes
|
||||
#include <fileref.h>
|
||||
#include <tag.h>
|
||||
|
||||
#include "curses/menu_impl.h"
|
||||
#include "screens/browser.h"
|
||||
#include "charset.h"
|
||||
#include "display.h"
|
||||
#include "helpers.h"
|
||||
#include "global.h"
|
||||
#include "screens/song_info.h"
|
||||
#include "screens/playlist.h"
|
||||
#include "screens/search_engine.h"
|
||||
#include "statusbar.h"
|
||||
#include "screens/tag_editor.h"
|
||||
#include "title.h"
|
||||
#include "tags.h"
|
||||
#include "screens/screen_switcher.h"
|
||||
#include "utility/string.h"
|
||||
|
||||
using Global::MainHeight;
|
||||
using Global::MainStartY;
|
||||
|
||||
TinyTagEditor *myTinyTagEditor;
|
||||
|
||||
TinyTagEditor::TinyTagEditor()
|
||||
: Screen(NC::Menu<NC::Buffer>(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border()))
|
||||
{
|
||||
w.setHighlightColor(Config.main_highlight_color);
|
||||
w.cyclicScrolling(Config.use_cyclic_scrolling);
|
||||
w.centeredCursor(Config.centered_cursor);
|
||||
w.setItemDisplayer([](NC::Menu<NC::Buffer> &menu) {
|
||||
menu << menu.drawn()->value();
|
||||
});
|
||||
}
|
||||
|
||||
void TinyTagEditor::resize()
|
||||
{
|
||||
size_t x_offset, width;
|
||||
getWindowResizeParams(x_offset, width);
|
||||
w.resize(width, MainHeight);
|
||||
w.moveTo(x_offset, MainStartY);
|
||||
hasToBeResized = 0;
|
||||
}
|
||||
|
||||
void TinyTagEditor::switchTo()
|
||||
{
|
||||
using Global::myScreen;
|
||||
if (itsEdited.isStream())
|
||||
{
|
||||
Statusbar::print("Streams can't be edited");
|
||||
}
|
||||
else if (getTags())
|
||||
{
|
||||
m_previous_screen = myScreen;
|
||||
SwitchTo::execute(this);
|
||||
drawHeader();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string full_path;
|
||||
if (itsEdited.isFromDatabase())
|
||||
full_path += Config.mpd_music_dir;
|
||||
full_path += itsEdited.getURI();
|
||||
|
||||
const char msg[] = "Couldn't read file \"%1%\"";
|
||||
Statusbar::printf(msg, wideShorten(full_path, COLS-const_strlen(msg)));
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring TinyTagEditor::title()
|
||||
{
|
||||
return L"Tiny tag editor";
|
||||
}
|
||||
|
||||
void TinyTagEditor::mouseButtonPressed(MEVENT me)
|
||||
{
|
||||
if (w.empty() || !w.hasCoords(me.x, me.y) || size_t(me.y) >= w.size())
|
||||
return;
|
||||
if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
|
||||
{
|
||||
if (!w.Goto(me.y))
|
||||
return;
|
||||
if (me.bstate & BUTTON3_PRESSED)
|
||||
{
|
||||
w.refresh();
|
||||
runAction();
|
||||
}
|
||||
}
|
||||
else
|
||||
Screen<WindowType>::mouseButtonPressed(me);
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
bool TinyTagEditor::actionRunnable()
|
||||
{
|
||||
return !w.empty();
|
||||
}
|
||||
|
||||
void TinyTagEditor::runAction()
|
||||
{
|
||||
size_t option = w.choice();
|
||||
if (option < 19) // separator after comment
|
||||
{
|
||||
Statusbar::ScopedLock slock;
|
||||
size_t pos = option-8;
|
||||
Statusbar::put() << NC::Format::Bold << SongInfo::Tags[pos].Name << ": " << NC::Format::NoBold;
|
||||
itsEdited.setTags(SongInfo::Tags[pos].Set, Global::wFooter->prompt(
|
||||
itsEdited.getTags(SongInfo::Tags[pos].Get)));
|
||||
w.at(option).value().clear();
|
||||
w.at(option).value() << NC::Format::Bold << SongInfo::Tags[pos].Name << ':' << NC::Format::NoBold << ' ';
|
||||
ShowTag(w.at(option).value(), itsEdited.getTags(SongInfo::Tags[pos].Get));
|
||||
}
|
||||
else if (option == 20)
|
||||
{
|
||||
Statusbar::ScopedLock slock;
|
||||
Statusbar::put() << NC::Format::Bold << "Filename: " << NC::Format::NoBold;
|
||||
std::string filename = itsEdited.getNewName().empty() ? itsEdited.getName() : itsEdited.getNewName();
|
||||
size_t dot = filename.rfind(".");
|
||||
std::string extension = filename.substr(dot);
|
||||
filename = filename.substr(0, dot);
|
||||
std::string new_name = Global::wFooter->prompt(filename);
|
||||
if (!new_name.empty())
|
||||
{
|
||||
itsEdited.setNewName(new_name + extension);
|
||||
w.at(option).value().clear();
|
||||
w.at(option).value() << NC::Format::Bold << "Filename:" << NC::Format::NoBold << ' ' << (itsEdited.getNewName().empty() ? itsEdited.getName() : itsEdited.getNewName());
|
||||
}
|
||||
}
|
||||
|
||||
if (option == 22)
|
||||
{
|
||||
Statusbar::print("Updating tags...");
|
||||
if (Tags::write(itsEdited))
|
||||
{
|
||||
Statusbar::print("Tags updated");
|
||||
if (itsEdited.isFromDatabase())
|
||||
Mpd.UpdateDirectory(itsEdited.getDirectory());
|
||||
else
|
||||
{
|
||||
if (m_previous_screen == myPlaylist)
|
||||
myPlaylist->main().current()->value() = itsEdited;
|
||||
else if (m_previous_screen == myBrowser)
|
||||
myBrowser->requestUpdate();
|
||||
}
|
||||
}
|
||||
else
|
||||
Statusbar::printf("Error while writing tags: %1%", strerror(errno));
|
||||
}
|
||||
if (option > 21)
|
||||
m_previous_screen->switchTo();
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
void TinyTagEditor::SetEdited(const MPD::Song &s)
|
||||
{
|
||||
if (auto ms = dynamic_cast<const MPD::MutableSong *>(&s))
|
||||
itsEdited = *ms;
|
||||
else
|
||||
itsEdited = s;
|
||||
}
|
||||
|
||||
bool TinyTagEditor::getTags()
|
||||
{
|
||||
std::string path_to_file;
|
||||
if (itsEdited.isFromDatabase())
|
||||
path_to_file += Config.mpd_music_dir;
|
||||
path_to_file += itsEdited.getURI();
|
||||
|
||||
TagLib::FileRef f(path_to_file.c_str());
|
||||
if (f.isNull())
|
||||
return false;
|
||||
|
||||
std::string ext = itsEdited.getURI();
|
||||
ext = boost::locale::to_lower(ext.substr(ext.rfind(".")+1));
|
||||
|
||||
w.clear();
|
||||
w.reset();
|
||||
|
||||
w.resizeList(24);
|
||||
|
||||
for (size_t i = 0; i < 7; ++i)
|
||||
w.at(i).setInactive(true);
|
||||
|
||||
w.at(7).setSeparator(true);
|
||||
w.at(19).setSeparator(true);
|
||||
w.at(21).setSeparator(true);
|
||||
|
||||
if (!Tags::extendedSetSupported(f.file()))
|
||||
{
|
||||
w.at(10).setInactive(true);
|
||||
for (size_t i = 15; i <= 17; ++i)
|
||||
w.at(i).setInactive(true);
|
||||
}
|
||||
|
||||
w.highlight(8);
|
||||
|
||||
w.at(0).value() << NC::Format::Bold << Config.color1 << "Song name: " << NC::Format::NoBold << Config.color2 << itsEdited.getName() << NC::Color::End;
|
||||
w.at(1).value() << NC::Format::Bold << Config.color1 << "Location in DB: " << NC::Format::NoBold << Config.color2;
|
||||
ShowTag(w.at(1).value(), itsEdited.getDirectory());
|
||||
w.at(1).value() << NC::Color::End;
|
||||
w.at(3).value() << NC::Format::Bold << Config.color1 << "Length: " << NC::Format::NoBold << Config.color2 << itsEdited.getLength() << NC::Color::End;
|
||||
w.at(4).value() << NC::Format::Bold << Config.color1 << "Bitrate: " << NC::Format::NoBold << Config.color2 << f.audioProperties()->bitrate() << " kbps" << NC::Color::End;
|
||||
w.at(5).value() << NC::Format::Bold << Config.color1 << "Sample rate: " << NC::Format::NoBold << Config.color2 << f.audioProperties()->sampleRate() << " Hz" << NC::Color::End;
|
||||
w.at(6).value() << NC::Format::Bold << Config.color1 << "Channels: " << NC::Format::NoBold << Config.color2 << (f.audioProperties()->channels() == 1 ? "Mono" : "Stereo") << NC::Color::Default;
|
||||
|
||||
unsigned pos = 8;
|
||||
for (const SongInfo::Metadata *m = SongInfo::Tags; m->Name; ++m, ++pos)
|
||||
{
|
||||
w.at(pos).value() << NC::Format::Bold << m->Name << ":" << NC::Format::NoBold << ' ';
|
||||
ShowTag(w.at(pos).value(), itsEdited.getTags(m->Get));
|
||||
}
|
||||
|
||||
w.at(20).value() << NC::Format::Bold << "Filename:" << NC::Format::NoBold << ' ' << itsEdited.getName();
|
||||
|
||||
w.at(22).value() << "Save";
|
||||
w.at(23).value() << "Cancel";
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // HAVE_TAGLIB_H
|
||||
|
||||
68
src/screens/tiny_tag_editor.h
Normal file
68
src/screens/tiny_tag_editor.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_TINY_TAG_EDITOR_H
|
||||
#define NCMPCPP_TINY_TAG_EDITOR_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_TAGLIB_H
|
||||
|
||||
#include "interfaces.h"
|
||||
#include "mutable_song.h"
|
||||
#include "screens/screen.h"
|
||||
|
||||
struct TinyTagEditor: Screen<NC::Menu<NC::Buffer>>, HasActions
|
||||
{
|
||||
TinyTagEditor();
|
||||
|
||||
// Screen< NC::Menu<NC::Buffer> > implementation
|
||||
virtual void resize() override;
|
||||
virtual void switchTo() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::TinyTagEditor; }
|
||||
|
||||
virtual void update() override { }
|
||||
|
||||
virtual void mouseButtonPressed(MEVENT me) override;
|
||||
|
||||
virtual bool isLockable() override { return false; }
|
||||
virtual bool isMergable() override { return true; }
|
||||
|
||||
// HasActions implementation
|
||||
virtual bool actionRunnable() override;
|
||||
virtual void runAction() override;
|
||||
|
||||
// private members
|
||||
void SetEdited(const MPD::Song &);
|
||||
|
||||
private:
|
||||
bool getTags();
|
||||
MPD::MutableSong itsEdited;
|
||||
BaseScreen *m_previous_screen;
|
||||
};
|
||||
|
||||
extern TinyTagEditor *myTinyTagEditor;
|
||||
|
||||
#endif // HAVE_TAGLIB_H
|
||||
|
||||
#endif // NCMPCPP_TINY_TAG_EDITOR_H
|
||||
|
||||
499
src/screens/visualizer.cpp
Normal file
499
src/screens/visualizer.cpp
Normal file
@@ -0,0 +1,499 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "screens/visualizer.h"
|
||||
|
||||
#ifdef ENABLE_VISUALIZER
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/math/constants/constants.hpp>
|
||||
#include <cerrno>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "global.h"
|
||||
#include "settings.h"
|
||||
#include "status.h"
|
||||
#include "statusbar.h"
|
||||
#include "title.h"
|
||||
#include "screens/screen_switcher.h"
|
||||
#include "status.h"
|
||||
#include "enums.h"
|
||||
|
||||
using Samples = std::vector<int16_t>;
|
||||
|
||||
using Global::MainStartY;
|
||||
using Global::MainHeight;
|
||||
|
||||
Visualizer *myVisualizer;
|
||||
|
||||
namespace {
|
||||
|
||||
const int fps = 25;
|
||||
|
||||
// toColor: a scaling function for coloring. For numbers 0 to max this function returns
|
||||
// a coloring from the lowest color to the highest, and colors will not loop from 0 to max.
|
||||
const NC::Color &toColor(size_t number, size_t max, bool wrap = true)
|
||||
{
|
||||
const auto colors_size = Config.visualizer_colors.size();
|
||||
const auto index = (number * colors_size) / max;
|
||||
return Config.visualizer_colors[
|
||||
wrap ? index % colors_size : std::min(index, colors_size-1)
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Visualizer::Visualizer()
|
||||
: Screen(NC::Window(0, MainStartY, COLS, MainHeight, "", NC::Color::Default, NC::Border()))
|
||||
{
|
||||
ResetFD();
|
||||
m_samples = 44100/fps;
|
||||
if (Config.visualizer_in_stereo)
|
||||
m_samples *= 2;
|
||||
# ifdef HAVE_FFTW3_H
|
||||
m_fftw_results = m_samples/2+1;
|
||||
m_freq_magnitudes.resize(m_fftw_results);
|
||||
m_fftw_input = static_cast<double *>(fftw_malloc(sizeof(double)*m_samples));
|
||||
m_fftw_output = static_cast<fftw_complex *>(fftw_malloc(sizeof(fftw_complex)*m_fftw_results));
|
||||
m_fftw_plan = fftw_plan_dft_r2c_1d(m_samples, m_fftw_input, m_fftw_output, FFTW_ESTIMATE);
|
||||
# endif // HAVE_FFTW3_H
|
||||
}
|
||||
|
||||
void Visualizer::switchTo()
|
||||
{
|
||||
SwitchTo::execute(this);
|
||||
w.clear();
|
||||
SetFD();
|
||||
m_timer = boost::posix_time::from_time_t(0);
|
||||
drawHeader();
|
||||
}
|
||||
|
||||
void Visualizer::resize()
|
||||
{
|
||||
size_t x_offset, width;
|
||||
getWindowResizeParams(x_offset, width);
|
||||
w.resize(width, MainHeight);
|
||||
w.moveTo(x_offset, MainStartY);
|
||||
hasToBeResized = 0;
|
||||
}
|
||||
|
||||
std::wstring Visualizer::title()
|
||||
{
|
||||
return L"Music visualizer";
|
||||
}
|
||||
|
||||
void Visualizer::update()
|
||||
{
|
||||
if (m_fifo < 0)
|
||||
return;
|
||||
|
||||
// PCM in format 44100:16:1 (for mono visualization) and
|
||||
// 44100:16:2 (for stereo visualization) is supported.
|
||||
Samples samples(m_samples);
|
||||
ssize_t data = read(m_fifo, samples.data(),
|
||||
samples.size() * sizeof(Samples::value_type));
|
||||
if (data < 0) // no data available in fifo
|
||||
return;
|
||||
|
||||
if (m_output_id != -1 && Global::Timer - m_timer > Config.visualizer_sync_interval)
|
||||
{
|
||||
Mpd.DisableOutput(m_output_id);
|
||||
usleep(50000);
|
||||
Mpd.EnableOutput(m_output_id);
|
||||
m_timer = Global::Timer;
|
||||
}
|
||||
|
||||
void (Visualizer::*draw)(int16_t *, ssize_t, size_t, size_t);
|
||||
void (Visualizer::*drawStereo)(int16_t *, int16_t *, ssize_t, size_t);
|
||||
# ifdef HAVE_FFTW3_H
|
||||
if (Config.visualizer_type == VisualizerType::Spectrum)
|
||||
{
|
||||
draw = &Visualizer::DrawFrequencySpectrum;
|
||||
drawStereo = &Visualizer::DrawFrequencySpectrumStereo;
|
||||
}
|
||||
else
|
||||
# endif // HAVE_FFTW3_H
|
||||
if (Config.visualizer_type == VisualizerType::WaveFilled)
|
||||
{
|
||||
draw = &Visualizer::DrawSoundWaveFill;
|
||||
drawStereo = &Visualizer::DrawSoundWaveFillStereo;
|
||||
}
|
||||
else if (Config.visualizer_type == VisualizerType::Ellipse)
|
||||
{
|
||||
draw = &Visualizer::DrawSoundEllipse;
|
||||
drawStereo = &Visualizer::DrawSoundEllipseStereo;
|
||||
}
|
||||
else
|
||||
{
|
||||
draw = &Visualizer::DrawSoundWave;
|
||||
drawStereo = &Visualizer::DrawSoundWaveStereo;
|
||||
}
|
||||
|
||||
const ssize_t samples_read = data/sizeof(int16_t);
|
||||
m_auto_scale_multiplier += 1.0/fps;
|
||||
for (auto &sample : samples)
|
||||
{
|
||||
double scale = std::numeric_limits<int16_t>::min();
|
||||
scale /= sample;
|
||||
scale = fabs(scale);
|
||||
if (scale < m_auto_scale_multiplier)
|
||||
m_auto_scale_multiplier = scale;
|
||||
}
|
||||
for (auto &sample : samples)
|
||||
{
|
||||
int32_t tmp = sample;
|
||||
if (m_auto_scale_multiplier <= 50.0) // limit the auto scale
|
||||
tmp *= m_auto_scale_multiplier;
|
||||
if (tmp < std::numeric_limits<int16_t>::min())
|
||||
sample = std::numeric_limits<int16_t>::min();
|
||||
else if (tmp > std::numeric_limits<int16_t>::max())
|
||||
sample = std::numeric_limits<int16_t>::max();
|
||||
else
|
||||
sample = tmp;
|
||||
}
|
||||
|
||||
w.clear();
|
||||
if (Config.visualizer_in_stereo)
|
||||
{
|
||||
auto chan_samples = samples_read/2;
|
||||
int16_t buf_left[chan_samples], buf_right[chan_samples];
|
||||
for (ssize_t i = 0, j = 0; i < samples_read; i += 2, ++j)
|
||||
{
|
||||
buf_left[j] = samples[i];
|
||||
buf_right[j] = samples[i+1];
|
||||
}
|
||||
size_t half_height = w.getHeight()/2;
|
||||
|
||||
(this->*drawStereo)(buf_left, buf_right, chan_samples, half_height);
|
||||
}
|
||||
else
|
||||
{
|
||||
(this->*draw)(samples.data(), samples_read, 0, w.getHeight());
|
||||
}
|
||||
w.refresh();
|
||||
}
|
||||
|
||||
int Visualizer::windowTimeout()
|
||||
{
|
||||
if (m_fifo >= 0 && Status::State::player() == MPD::psPlay)
|
||||
return 1000/fps;
|
||||
else
|
||||
return Screen<WindowType>::windowTimeout();
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
void Visualizer::DrawSoundWave(int16_t *buf, ssize_t samples, size_t y_offset, size_t height)
|
||||
{
|
||||
const size_t half_height = height/2;
|
||||
const size_t base_y = y_offset+half_height;
|
||||
const size_t win_width = w.getWidth();
|
||||
const int samples_per_column = samples/win_width;
|
||||
|
||||
// too little samples
|
||||
if (samples_per_column == 0)
|
||||
return;
|
||||
|
||||
auto draw_point = [&](size_t x, int32_t y) {
|
||||
w << NC::XY(x, base_y+y)
|
||||
<< toColor(std::abs(y), half_height, false)
|
||||
<< Config.visualizer_chars[0]
|
||||
<< NC::Color::End;
|
||||
};
|
||||
|
||||
int32_t point_y, prev_point_y = 0;
|
||||
for (size_t x = 0; x < win_width; ++x)
|
||||
{
|
||||
point_y = 0;
|
||||
// calculate mean from the relevant points
|
||||
for (int j = 0; j < samples_per_column; ++j)
|
||||
point_y += buf[x*samples_per_column+j];
|
||||
point_y /= samples_per_column;
|
||||
// normalize it to fit the screen
|
||||
point_y *= height / 65536.0;
|
||||
|
||||
draw_point(x, point_y);
|
||||
|
||||
// if the gap between two consecutive points is too big,
|
||||
// intermediate values are needed for the wave to be watchable.
|
||||
if (x > 0 && std::abs(prev_point_y-point_y) > 1)
|
||||
{
|
||||
const int32_t half = (prev_point_y+point_y)/2;
|
||||
if (prev_point_y < point_y)
|
||||
{
|
||||
for (auto y = prev_point_y; y < point_y; ++y)
|
||||
draw_point(x-(y < half), y);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto y = prev_point_y; y > point_y; --y)
|
||||
draw_point(x-(y > half), y);
|
||||
}
|
||||
}
|
||||
prev_point_y = point_y;
|
||||
}
|
||||
}
|
||||
|
||||
void Visualizer::DrawSoundWaveStereo(int16_t *buf_left, int16_t *buf_right, ssize_t samples, size_t height)
|
||||
{
|
||||
DrawSoundWave(buf_left, samples, 0, height);
|
||||
DrawSoundWave(buf_right, samples, height, w.getHeight() - height);
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
// DrawSoundWaveFill: This visualizer is very similar to DrawSoundWave, but instead of
|
||||
// a single line the entire height is filled. In stereo mode, the top half of the screen
|
||||
// is dedicated to the right channel, the bottom the left channel.
|
||||
void Visualizer::DrawSoundWaveFill(int16_t *buf, ssize_t samples, size_t y_offset, size_t height)
|
||||
{
|
||||
// if right channel is drawn, bars descend from the top to the bottom
|
||||
const bool flipped = y_offset > 0;
|
||||
const size_t win_width = w.getWidth();
|
||||
const int samples_per_column = samples/win_width;
|
||||
|
||||
// too little samples
|
||||
if (samples_per_column == 0)
|
||||
return;
|
||||
|
||||
int32_t point_y;
|
||||
for (size_t x = 0; x < win_width; ++x)
|
||||
{
|
||||
point_y = 0;
|
||||
// calculate mean from the relevant points
|
||||
for (int j = 0; j < samples_per_column; ++j)
|
||||
point_y += buf[x*samples_per_column+j];
|
||||
point_y /= samples_per_column;
|
||||
// normalize it to fit the screen
|
||||
point_y = std::abs(point_y);
|
||||
point_y *= height / 32768.0;
|
||||
|
||||
for (int32_t j = 0; j < point_y; ++j)
|
||||
{
|
||||
size_t y = flipped ? y_offset+j : y_offset+height-j-1;
|
||||
w << NC::XY(x, y)
|
||||
<< toColor(j, height)
|
||||
<< Config.visualizer_chars[1]
|
||||
<< NC::Color::End;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Visualizer::DrawSoundWaveFillStereo(int16_t *buf_left, int16_t *buf_right, ssize_t samples, size_t height)
|
||||
{
|
||||
DrawSoundWaveFill(buf_left, samples, 0, height);
|
||||
DrawSoundWaveFill(buf_right, samples, height, w.getHeight() - height);
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
// draws the sound wave as an ellipse with origin in the center of the screen
|
||||
void Visualizer::DrawSoundEllipse(int16_t *buf, ssize_t samples, size_t, size_t height)
|
||||
{
|
||||
const size_t half_width = w.getWidth()/2;
|
||||
const size_t half_height = height/2;
|
||||
|
||||
// make it so that the loop goes around the ellipse exactly once
|
||||
const double deg_multiplier = 2*boost::math::constants::pi<double>()/samples;
|
||||
|
||||
int32_t x, y;
|
||||
double radius, max_radius;
|
||||
for (ssize_t i = 0; i < samples; ++i)
|
||||
{
|
||||
x = half_width * std::cos(i*deg_multiplier);
|
||||
y = half_height * std::sin(i*deg_multiplier);
|
||||
max_radius = sqrt(x*x + y*y);
|
||||
|
||||
// calculate the distance of the sample from the center,
|
||||
// where 0 is the center of the ellipse and 1 is its border
|
||||
radius = std::abs(buf[i]);
|
||||
radius /= 32768.0;
|
||||
|
||||
// appropriately scale the position
|
||||
x *= radius;
|
||||
y *= radius;
|
||||
|
||||
w << NC::XY(half_width + x, half_height + y)
|
||||
<< toColor(sqrt(x*x + y*y), max_radius, false)
|
||||
<< Config.visualizer_chars[0]
|
||||
<< NC::Color::End;
|
||||
}
|
||||
}
|
||||
|
||||
// DrawSoundEllipseStereo: This visualizer only works in stereo. The colors form concentric
|
||||
// rings originating from the center (width/2, height/2). For any given point, the width is
|
||||
// scaled with the left channel and height is scaled with the right channel. For example,
|
||||
// if a song is entirely in the right channel, then it would just be a vertical line.
|
||||
//
|
||||
// Since every font/terminal is different, the visualizer is never a perfect circle. This
|
||||
// visualizer assume the font height is twice the length of the font's width. If the font
|
||||
// is skinner or wider than this, instead of a circle it will be an ellipse.
|
||||
void Visualizer::DrawSoundEllipseStereo(int16_t *buf_left, int16_t *buf_right, ssize_t samples, size_t half_height)
|
||||
{
|
||||
const size_t width = w.getWidth();
|
||||
const size_t left_half_width = width/2;
|
||||
const size_t right_half_width = width - left_half_width;
|
||||
const size_t top_half_height = half_height;
|
||||
const size_t bottom_half_height = w.getHeight() - half_height;
|
||||
|
||||
// Makes the radius of each ring be approximately 2 cells wide.
|
||||
const int32_t radius = 2*Config.visualizer_colors.size();
|
||||
int32_t x, y;
|
||||
for (ssize_t i = 0; i < samples; ++i)
|
||||
{
|
||||
x = buf_left[i]/32768.0 * (buf_left[i] < 0 ? left_half_width : right_half_width);
|
||||
y = buf_right[i]/32768.0 * (buf_right[i] < 0 ? top_half_height : bottom_half_height);
|
||||
|
||||
// The arguments to the toColor function roughly follow a circle equation where
|
||||
// the center is not centered around (0,0). For example (x - w)^2 + (y-h)+2 = r^2
|
||||
// centers the circle around the point (w,h). Because fonts are not all the same
|
||||
// size, this will not always generate a perfect circle.
|
||||
w << toColor(sqrt(x*x + 4*y*y), radius)
|
||||
<< NC::XY(left_half_width + x, top_half_height + y)
|
||||
<< Config.visualizer_chars[1]
|
||||
<< NC::Color::End;
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
#ifdef HAVE_FFTW3_H
|
||||
void Visualizer::DrawFrequencySpectrum(int16_t *buf, ssize_t samples, size_t y_offset, size_t height)
|
||||
{
|
||||
// if right channel is drawn, bars descend from the top to the bottom
|
||||
const bool flipped = y_offset > 0;
|
||||
|
||||
// copy samples to fftw input array
|
||||
for (unsigned i = 0; i < m_samples; ++i)
|
||||
m_fftw_input[i] = i < samples ? buf[i] : 0;
|
||||
fftw_execute(m_fftw_plan);
|
||||
|
||||
// count magnitude of each frequency and scale it to fit the screen
|
||||
for (size_t i = 0; i < m_fftw_results; ++i)
|
||||
m_freq_magnitudes[i] = sqrt(
|
||||
m_fftw_output[i][0]*m_fftw_output[i][0]
|
||||
+ m_fftw_output[i][1]*m_fftw_output[i][1]
|
||||
)/2e4*height;
|
||||
|
||||
const size_t win_width = w.getWidth();
|
||||
// cut bandwidth a little to achieve better look
|
||||
const double bins_per_bar = m_fftw_results/win_width * 7/10;
|
||||
double bar_height;
|
||||
size_t bar_bound_height;
|
||||
for (size_t x = 0; x < win_width; ++x)
|
||||
{
|
||||
bar_height = 0;
|
||||
for (int j = 0; j < bins_per_bar; ++j)
|
||||
bar_height += m_freq_magnitudes[x*bins_per_bar+j];
|
||||
// buff higher frequencies
|
||||
bar_height *= log2(2 + x) * 100.0/win_width;
|
||||
// moderately normalize the heights
|
||||
bar_height = pow(bar_height, 0.5);
|
||||
|
||||
bar_bound_height = std::min(std::size_t(bar_height/bins_per_bar), height);
|
||||
for (size_t j = 0; j < bar_bound_height; ++j)
|
||||
{
|
||||
size_t y = flipped ? y_offset+j : y_offset+height-j-1;
|
||||
w << NC::XY(x, y)
|
||||
<< toColor(j, height)
|
||||
<< Config.visualizer_chars[1]
|
||||
<< NC::Color::End;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Visualizer::DrawFrequencySpectrumStereo(int16_t *buf_left, int16_t *buf_right, ssize_t samples, size_t height)
|
||||
{
|
||||
DrawFrequencySpectrum(buf_left, samples, 0, height);
|
||||
DrawFrequencySpectrum(buf_right, samples, height, w.getHeight() - height);
|
||||
}
|
||||
#endif // HAVE_FFTW3_H
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
void Visualizer::ToggleVisualizationType()
|
||||
{
|
||||
switch (Config.visualizer_type)
|
||||
{
|
||||
case VisualizerType::Wave:
|
||||
Config.visualizer_type = VisualizerType::WaveFilled;
|
||||
break;
|
||||
case VisualizerType::WaveFilled:
|
||||
# ifdef HAVE_FFTW3_H
|
||||
Config.visualizer_type = VisualizerType::Spectrum;
|
||||
# else
|
||||
Config.visualizer_type = VisualizerType::Ellipse;
|
||||
# endif // HAVE_FFTW3_H
|
||||
break;
|
||||
# ifdef HAVE_FFTW3_H
|
||||
case VisualizerType::Spectrum:
|
||||
Config.visualizer_type = VisualizerType::Ellipse;
|
||||
break;
|
||||
# endif // HAVE_FFTW3_H
|
||||
case VisualizerType::Ellipse:
|
||||
Config.visualizer_type = VisualizerType::Wave;
|
||||
break;
|
||||
}
|
||||
Statusbar::printf("Visualization type: %1%", Config.visualizer_type);
|
||||
}
|
||||
|
||||
void Visualizer::SetFD()
|
||||
{
|
||||
if (m_fifo < 0 && (m_fifo = open(Config.visualizer_fifo_path.c_str(), O_RDONLY | O_NONBLOCK)) < 0)
|
||||
Statusbar::printf("Couldn't open \"%1%\" for reading PCM data: %2%",
|
||||
Config.visualizer_fifo_path, strerror(errno)
|
||||
);
|
||||
}
|
||||
|
||||
void Visualizer::ResetFD()
|
||||
{
|
||||
m_fifo = -1;
|
||||
}
|
||||
|
||||
void Visualizer::FindOutputID()
|
||||
{
|
||||
m_output_id = -1;
|
||||
if (!Config.visualizer_output_name.empty())
|
||||
{
|
||||
for (MPD::OutputIterator out = Mpd.GetOutputs(), end; out != end; ++out)
|
||||
{
|
||||
if (out->name() == Config.visualizer_output_name)
|
||||
{
|
||||
m_output_id = out->id();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m_output_id == -1)
|
||||
Statusbar::printf("There is no output named \"%s\"", Config.visualizer_output_name);
|
||||
}
|
||||
}
|
||||
|
||||
void Visualizer::ResetAutoScaleMultiplier()
|
||||
{
|
||||
m_auto_scale_multiplier = std::numeric_limits<double>::infinity();
|
||||
}
|
||||
|
||||
#endif // ENABLE_VISUALIZER
|
||||
|
||||
/* vim: set tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab : */
|
||||
98
src/screens/visualizer.h
Normal file
98
src/screens/visualizer.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008-2016 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef NCMPCPP_VISUALIZER_H
|
||||
#define NCMPCPP_VISUALIZER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef ENABLE_VISUALIZER
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time_types.hpp>
|
||||
#include "curses/window.h"
|
||||
#include "interfaces.h"
|
||||
#include "screens/screen.h"
|
||||
|
||||
#ifdef HAVE_FFTW3_H
|
||||
# include <fftw3.h>
|
||||
#endif
|
||||
|
||||
struct Visualizer: Screen<NC::Window>, Tabbable
|
||||
{
|
||||
Visualizer();
|
||||
|
||||
virtual void switchTo() override;
|
||||
virtual void resize() override;
|
||||
|
||||
virtual std::wstring title() override;
|
||||
virtual ScreenType type() override { return ScreenType::Visualizer; }
|
||||
|
||||
virtual void update() override;
|
||||
virtual void scroll(NC::Scroll) override { }
|
||||
|
||||
virtual int windowTimeout() override;
|
||||
|
||||
virtual void mouseButtonPressed(MEVENT) override { }
|
||||
|
||||
virtual bool isLockable() override { return true; }
|
||||
virtual bool isMergable() override { return true; }
|
||||
|
||||
// private members
|
||||
void ToggleVisualizationType();
|
||||
void SetFD();
|
||||
void ResetFD();
|
||||
void FindOutputID();
|
||||
void ResetAutoScaleMultiplier();
|
||||
|
||||
private:
|
||||
void DrawSoundWave(int16_t *, ssize_t, size_t, size_t);
|
||||
void DrawSoundWaveStereo(int16_t *, int16_t *, ssize_t, size_t);
|
||||
void DrawSoundWaveFill(int16_t *, ssize_t, size_t, size_t);
|
||||
void DrawSoundWaveFillStereo(int16_t *, int16_t *, ssize_t, size_t);
|
||||
void DrawSoundEllipse(int16_t *, ssize_t, size_t, size_t);
|
||||
void DrawSoundEllipseStereo(int16_t *, int16_t *, ssize_t, size_t);
|
||||
# ifdef HAVE_FFTW3_H
|
||||
void DrawFrequencySpectrum(int16_t *, ssize_t, size_t, size_t);
|
||||
void DrawFrequencySpectrumStereo(int16_t *, int16_t *, ssize_t, size_t);
|
||||
# endif // HAVE_FFTW3_H
|
||||
|
||||
int m_output_id;
|
||||
boost::posix_time::ptime m_timer;
|
||||
|
||||
int m_fifo;
|
||||
size_t m_samples;
|
||||
double m_auto_scale_multiplier;
|
||||
# ifdef HAVE_FFTW3_H
|
||||
size_t m_fftw_results;
|
||||
double *m_fftw_input;
|
||||
fftw_complex *m_fftw_output;
|
||||
fftw_plan m_fftw_plan;
|
||||
|
||||
std::vector<double> m_freq_magnitudes;
|
||||
# endif // HAVE_FFTW3_H
|
||||
};
|
||||
|
||||
extern Visualizer *myVisualizer;
|
||||
|
||||
#endif // ENABLE_VISUALIZER
|
||||
|
||||
#endif // NCMPCPP_VISUALIZER_H
|
||||
|
||||
/* vim: set tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab : */
|
||||
Reference in New Issue
Block a user