Move screens to subdirectory

This commit is contained in:
Andrzej Rybczak
2016-12-22 15:58:57 +01:00
parent 478f4454d8
commit 5445c41aaa
53 changed files with 179 additions and 179 deletions

761
src/screens/browser.cpp Normal file
View 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
View 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 &currentDirectory();
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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

160
src/screens/media_library.h Normal file
View 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
View 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
View 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
View 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
View 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

View 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);
}
}

View 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
View 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
View 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

View 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
View 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
View 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

View 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
View 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

View 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);
}

View 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
View 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
View 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
View 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
View 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

View 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;
}

View 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

File diff suppressed because it is too large Load Diff

125
src/screens/tag_editor.h Normal file
View 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

View 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

View 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
View 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
View 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 : */