Files
ncmpcpp/src/helpers.cpp

917 lines
19 KiB
C++

/***************************************************************************
* Copyright (C) 2008 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., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include <algorithm>
#include <iostream>
#include "helpers.h"
#include "tag_editor.h"
using namespace MPD;
extern Connection *Mpd;
extern Menu<Song> *mPlaylist;
extern Menu<MPD::Item> *mBrowser;
extern Window *wFooter;
extern NcmpcppScreen current_screen;
extern int lock_statusbar_delay;
extern time_t time_of_statusbar_lock;
extern bool messages_allowed;
extern bool block_progressbar_update;
extern bool block_statusbar_update;
extern bool allow_statusbar_unlock;
extern bool search_case_sensitive;
extern bool search_match_to_pattern;
const string term_type = getenv("TERM") ? getenv("TERM") : "";
bool ConnectToMPD()
{
if (!Mpd->Connect())
{
std::cout << "Cannot connect to mpd: " << Mpd->GetErrorMessage() << std::endl;
return false;
}
return true;
}
bool ParseArgv(int argc, char **argv)
{
using std::cout;
using std::endl;
vector<string> v;
v.reserve(argc-1);
for (int i = 1; i < argc; i++)
v.push_back(argv[i]);
bool exit = 0;
for (vector<string>::iterator it = v.begin(); it != v.end() && !exit; it++)
{
if (*it == "-v" || *it == "--version")
{
cout << "ncmpcpp version: " << VERSION << endl
<< "built with support for:"
# ifdef HAVE_CURL_CURL_H
<< " curl"
# endif
# ifdef HAVE_TAGLIB_H
<< " taglib"
# endif
# ifdef _UTF8
<< " unicode"
# endif
<< endl;
return 1;
}
else if (*it == "-?" || *it == "--help")
{
cout
<< "Usage: ncmpcpp [OPTION]...\n"
<< " -?, --help show this help message\n"
<< " -v, --version display version information\n\n"
<< " play start playing\n"
<< " pause pause the currently playing song\n"
<< " toggle toggle play/pause mode\n"
<< " stop stop playing\n"
<< " next play the next song\n"
<< " prev play the previous song\n"
<< " volume [+-]<num> adjusts volume by [+-]<num>\n"
;
return 1;
}
if (!ConnectToMPD())
return 1;
if (*it == "play")
{
Mpd->Play();
exit = 1;
}
else if (*it == "pause")
{
Mpd->Execute("pause \"1\"\n");
exit = 1;
}
else if (*it == "toggle")
{
Mpd->Execute("pause\n");
exit = 1;
}
else if (*it == "stop")
{
Mpd->Stop();
exit = 1;
}
else if (*it == "next")
{
Mpd->Next();
exit = 1;
}
else if (*it == "prev")
{
Mpd->Prev();
exit = 1;
}
else if (*it == "volume")
{
it++;
Mpd->UpdateStatus();
if (!Mpd->GetErrorMessage().empty())
{
cout << "Error: " << Mpd->GetErrorMessage() << endl;
return 1;
}
if (it != v.end())
Mpd->SetVolume(Mpd->GetVolume()+StrToInt(*it));
exit = 1;
}
else
{
cout << "ncmpcpp: invalid option " << *it << endl;
return 1;
}
if (!Mpd->GetErrorMessage().empty())
{
cout << "Error: " << Mpd->GetErrorMessage() << endl;
return 1;
}
}
return exit;
}
void LockStatusbar()
{
if (Config.statusbar_visibility)
block_statusbar_update = 1;
else
block_progressbar_update = 1;
allow_statusbar_unlock = 0;
}
void UnlockStatusbar()
{
allow_statusbar_unlock = 1;
if (lock_statusbar_delay < 0)
{
if (Config.statusbar_visibility)
block_statusbar_update = 0;
else
block_progressbar_update = 0;
}
}
bool CaseInsensitiveSorting::operator()(string a, string b)
{
ToLower(a);
ToLower(b);
return a < b;
}
bool CaseInsensitiveSorting::operator()(Song *sa, Song *sb)
{
string a = sa->GetName();
string b = sb->GetName();
ToLower(a);
ToLower(b);
return a < b;
}
bool CaseInsensitiveSorting::operator()(const Item &a, const Item &b)
{
if (a.type == b.type)
{
string sa = a.type == itSong ? a.song->GetName() : a.name;
string sb = b.type == itSong ? b.song->GetName() : b.name;
ToLower(sa);
ToLower(sb);
return sa < sb;
}
else
return a.type < b.type;
}
void UpdateSongList(Menu<Song> *menu)
{
bool bold = 0;
for (size_t i = 0; i < menu->Size(); i++)
{
for (size_t j = 0; j < mPlaylist->Size(); j++)
{
if (mPlaylist->at(j).GetHash() == menu->at(i).GetHash())
{
bold = 1;
break;
}
}
menu->BoldOption(i, bold);
bold = 0;
}
menu->Refresh();
}
bool Keypressed(int in, const int *key)
{
return in == key[0] || in == key[1];
}
bool SortSongsByTrack(Song *a, Song *b)
{
if (a->GetDisc() == b->GetDisc())
return StrToInt(a->GetTrack()) < StrToInt(b->GetTrack());
else
return StrToInt(a->GetDisc()) < StrToInt(b->GetDisc());
}
void WindowTitle(const string &status)
{
if (term_type != "linux" && Config.set_window_title)
std::cout << "\033]0;" << status << "\7";
}
void EscapeUnallowedChars(string &s)
{
const string unallowed_chars = "\"*/:<>?\\|";
for (string::const_iterator it = unallowed_chars.begin(); it != unallowed_chars.end(); it++)
{
for (size_t i = 0; i < s.length(); i++)
{
if (s[i] == *it)
{
s.erase(s.begin()+i);
i--;
}
}
}
}
Window &operator<<(Window &w, mpd_TagItems tag)
{
switch (tag)
{
case MPD_TAG_ITEM_ARTIST:
w << "Artist";
break;
case MPD_TAG_ITEM_ALBUM:
w << "Album";
break;
case MPD_TAG_ITEM_TITLE:
w << "Title";
break;
case MPD_TAG_ITEM_TRACK:
w << "Track";
break;
case MPD_TAG_ITEM_GENRE:
w << "Genre";
break;
case MPD_TAG_ITEM_DATE:
w << "Year";
break;
case MPD_TAG_ITEM_COMPOSER:
w << "Composer";
break;
case MPD_TAG_ITEM_PERFORMER:
w << "Performer";
break;
case MPD_TAG_ITEM_COMMENT:
w << "Comment";
break;
case MPD_TAG_ITEM_DISC:
w << "Disc";
break;
case MPD_TAG_ITEM_FILENAME:
w << "Filename";
break;
default:
break;
}
return w;
}
string IntoStr(mpd_TagItems tag)
{
switch (tag)
{
case MPD_TAG_ITEM_ARTIST:
return "Artist";
case MPD_TAG_ITEM_ALBUM:
return "Album";
case MPD_TAG_ITEM_TITLE:
return "Title";
case MPD_TAG_ITEM_TRACK:
return "Track";
case MPD_TAG_ITEM_GENRE:
return "Genre";
case MPD_TAG_ITEM_DATE:
return "Year";
case MPD_TAG_ITEM_COMPOSER:
return "Composer";
case MPD_TAG_ITEM_PERFORMER:
return "Performer";
case MPD_TAG_ITEM_COMMENT:
return "Comment";
case MPD_TAG_ITEM_DISC:
return "Disc";
case MPD_TAG_ITEM_FILENAME:
return "Filename";
default:
return "";
}
}
string FindSharedDir(const string &one, const string &two)
{
if (one == two)
return one;
string result;
size_t i = 1;
while (one.substr(0, i) == two.substr(0, i))
i++;
result = one.substr(0, i);
i = result.find_last_of("/");
return i != string::npos ? result.substr(0, i) : "/";
}
void DisplayTotalPlaylistLength(Window &w)
{
const int MINUTE = 60;
const int HOUR = 60*MINUTE;
const int DAY = 24*HOUR;
const int YEAR = 365*DAY;
int length = 0;
for (size_t i = 0; i < mPlaylist->Size(); i++)
length += mPlaylist->at(i).GetTotalLength();
w << '(' << mPlaylist->Size() << (mPlaylist->Size() == 1 ? " item" : " items");
if (length)
{
w << ", length: ";
int years = length/YEAR;
if (years)
{
w << years << (years == 1 ? " year" : " years");
length -= years*YEAR;
if (length)
w << ", ";
}
int days = length/DAY;
if (days)
{
w << days << (days == 1 ? " day" : " days");
length -= days*DAY;
if (length)
w << ", ";
}
int hours = length/HOUR;
if (hours)
{
w << hours << (hours == 1 ? " hour" : " hours");
length -= hours*HOUR;
if (length)
w << ", ";
}
int minutes = length/MINUTE;
if (minutes)
{
w << minutes << (minutes == 1 ? " minute" : " minutes");
length -= minutes*MINUTE;
if (length)
w << ", ";
}
if (length)
w << length << (length == 1 ? " second" : " seconds");
}
w << ')';
w.Refresh();
}
void DisplayStringPair(const StringPair &pair, void *, Menu<StringPair> *menu)
{
*menu << pair.first;
}
string DisplayColumns(string st)
{
string result;
size_t where = 0;
for (int width = StrToInt(GetLineValue(st, '(', ')', 1)); width; width = StrToInt(GetLineValue(st, '(', ')', 1)))
{
width *= COLS/100.0;
char type = GetLineValue(st, '{', '}', 1)[0];
switch (type)
{
case 'l':
result += "Time";
break;
case 'f':
result += "Filename";
break;
case 'F':
result += "Full filename";
break;
case 'a':
result += "Artist";
break;
case 't':
result += "Title";
break;
case 'b':
result += "Album";
break;
case 'y':
result += "Year";
break;
case 'n':
result += "Track";
break;
case 'g':
result += "Genre";
break;
case 'c':
result += "Composer";
break;
case 'p':
result += "Performer";
break;
case 'd':
result += "Disc";
break;
case 'C':
result += "Comment";
break;
default:
break;
}
where += width;
if (result.length() > where)
result = result.substr(0, where);
else
for (size_t i = result.length(); i <= where && i < size_t(COLS); i++, result += ' ') { }
}
return result;
}
void DisplaySongInColumns(const Song &s, void *s_template, Menu<Song> *menu)
{
string st = s_template ? *static_cast<string *>(s_template) : "";
size_t where = 0;
Color color;
for (int width = StrToInt(GetLineValue(st, '(', ')', 1)); width; width = StrToInt(GetLineValue(st, '(', ')', 1)))
{
if (where)
{
menu->GotoXY(where, menu->Y());
*menu << ' ';
if (color != clDefault)
*menu << clEnd;
}
width *= COLS/100.0;
color = IntoColor(GetLineValue(st, '[', ']', 1));
char type = GetLineValue(st, '{', '}', 1)[0];
string (Song::*get)() const = 0;
switch (type)
{
case 'l':
get = &Song::GetLength;
break;
case 'F':
get = &Song::GetFile;
break;
case 'f':
get = &Song::GetName;
break;
case 'a':
get = &Song::GetArtist;
break;
case 'b':
get = &Song::GetAlbum;
break;
case 'y':
get = &Song::GetYear;
break;
case 'n':
get = &Song::GetTrack;
break;
case 'g':
get = &Song::GetGenre;
break;
case 'c':
get = &Song::GetComposer;
break;
case 'p':
get = &Song::GetPerformer;
break;
case 'd':
get = &Song::GetDisc;
break;
case 'C':
get = &Song::GetComment;
break;
case 't':
if (!s.GetTitle().empty())
get = &Song::GetTitle;
else
get = &Song::GetName;
break;
default:
break;
}
if (color != clDefault)
*menu << color;
whline(menu->Raw(), 32, menu->GetWidth()-where);
string tag = (s.*get)();
if (!tag.empty())
*menu << tag;
else
*menu << Config.empty_tag;
where += width;
}
if (color != clDefault)
*menu << clEnd;
}
void DisplaySong(const Song &s, void *data, Menu<Song> *menu)
{
const string &song_template = data ? *static_cast<string *>(data) : "";
basic_buffer<my_char_t> buf;
bool right = 0;
string::const_iterator goto_pos, prev_pos;
for (string::const_iterator it = song_template.begin(); it != song_template.end(); it++)
{
CHECK_LINKED_TAGS:;
if (*it == '{')
{
prev_pos = it;
string (Song::*get)() const = 0;
for (; *it != '}'; it++)
{
if (*it == '%')
{
switch (*++it)
{
case 'l':
get = &Song::GetLength;
break;
case 'F':
get = &Song::GetFile;
break;
case 'f':
get = &Song::GetName;
break;
case 'a':
get = &Song::GetArtist;
break;
case 'b':
get = &Song::GetAlbum;
break;
case 'y':
get = &Song::GetYear;
break;
case 'n':
get = &Song::GetTrack;
break;
case 'g':
get = &Song::GetGenre;
break;
case 'c':
get = &Song::GetComposer;
break;
case 'p':
get = &Song::GetPerformer;
break;
case 'd':
get = &Song::GetDisc;
break;
case 'C':
get = &Song::GetComment;
break;
case 't':
get = &Song::GetTitle;
break;
default:
break;
}
if (get == &Song::GetLength)
{
if (!s.GetTotalLength())
break;
}
else if (get)
{
if ((s.*get)().empty())
break;
}
}
}
if (*it == '}')
{
while (1)
{
if (*it == '}' && *(it+1) != '|')
break;
it++;
}
goto_pos = ++it;
it = ++prev_pos;
}
else
{
for (; *it != '}'; it++) { }
it++;
if (it == song_template.end())
break;
if (*it == '{' || *it == '|')
{
if (*it == '|')
it++;
goto CHECK_LINKED_TAGS;
}
}
}
if (*it == '}')
{
if (goto_pos == song_template.end())
break;
it = goto_pos;
if (*it == '{')
goto CHECK_LINKED_TAGS;
}
if (*it != '%' && *it != '$')
{
if (!right)
*menu << *it;
else
buf << *it;
}
else if (*it == '%')
{
switch (*++it)
{
case 'l':
if (!right)
*menu << s.GetLength();
else
buf << TO_WSTRING(s.GetLength());
break;
case 'F':
if (!right)
*menu << s.GetFile();
else
buf << TO_WSTRING(s.GetFile());
break;
case 'f':
if (!right)
*menu << s.GetName();
else
buf << TO_WSTRING(s.GetName());
break;
case 'a':
if (!right)
*menu << s.GetArtist();
else
buf << TO_WSTRING(s.GetArtist());
break;
case 'b':
if (!right)
*menu << s.GetAlbum();
else
buf << TO_WSTRING(s.GetAlbum());
break;
case 'y':
if (!right)
*menu << s.GetYear();
else
buf << TO_WSTRING(s.GetYear());
break;
case 'n':
if (!right)
*menu << s.GetTrack();
else
buf << TO_WSTRING(s.GetTrack());
break;
case 'g':
if (!right)
*menu << s.GetGenre();
else
buf << TO_WSTRING(s.GetGenre());
break;
case 'c':
if (!right)
*menu << s.GetComposer();
else
buf << TO_WSTRING(s.GetComposer());
break;
case 'p':
if (!right)
*menu << s.GetPerformer();
else
buf << TO_WSTRING(s.GetPerformer());
break;
case 'd':
if (!right)
*menu << s.GetDisc();
else
buf << TO_WSTRING(s.GetDisc());
break;
case 'C':
if (!right)
*menu << s.GetComment();
else
buf << TO_WSTRING(s.GetComment());
break;
case 't':
if (!right)
*menu << s.GetTitle();
else
buf << TO_WSTRING(s.GetTitle());
break;
case 'r':
right = 1;
break;
default:
break;
}
}
else
{
it++;
if (!right)
*menu << Color(*it-'0');
else
buf << Color(*it-'0');
}
}
if (right)
{
menu->GotoXY(menu->GetWidth()-buf.Str().length(), menu->Y());
*menu << buf;
}
}
void GetInfo(Song &s, Scrollpad &info)
{
# ifdef HAVE_TAGLIB_H
string path_to_file;
if (s.IsFromDB())
path_to_file += Config.mpd_music_dir;
path_to_file += s.GetFile();
TagLib::FileRef f(path_to_file.c_str());
if (!f.isNull())
s.SetComment(f.tag()->comment().to8Bit(UNICODE));
# endif // HAVE_TAGLIB_H
info << fmtBold << Config.color1 << "Filename: " << fmtBoldEnd << Config.color2 << s.GetName() << "\n" << clEnd;
info << fmtBold << "Directory: " << fmtBoldEnd << Config.color2 << ShowTagInInfoScreen(s.GetDirectory()) << "\n\n" << clEnd;
info << fmtBold << "Length: " << fmtBoldEnd << Config.color2 << s.GetLength() << "\n" << clEnd;
# ifdef HAVE_TAGLIB_H
if (!f.isNull())
{
info << fmtBold << "Bitrate: " << fmtBoldEnd << Config.color2 << f.audioProperties()->bitrate() << " kbps\n" << clEnd;
info << fmtBold << "Sample rate: " << fmtBoldEnd << Config.color2 << f.audioProperties()->sampleRate() << " Hz\n" << clEnd;
info << fmtBold << "Channels: " << fmtBoldEnd << Config.color2 << (f.audioProperties()->channels() == 1 ? "Mono" : "Stereo") << "\n" << clDefault;
}
else
info << clDefault;
# endif // HAVE_TAGLIB_H
info << fmtBold << "\nTitle: " << fmtBoldEnd << ShowTagInInfoScreen(s.GetTitle());
info << fmtBold << "\nArtist: " << fmtBoldEnd << ShowTagInInfoScreen(s.GetArtist());
info << fmtBold << "\nAlbum: " << fmtBoldEnd << ShowTagInInfoScreen(s.GetAlbum());
info << fmtBold << "\nYear: " << fmtBoldEnd << ShowTagInInfoScreen(s.GetYear());
info << fmtBold << "\nTrack: " << fmtBoldEnd << ShowTagInInfoScreen(s.GetTrack());
info << fmtBold << "\nGenre: " << fmtBoldEnd << ShowTagInInfoScreen(s.GetGenre());
info << fmtBold << "\nComposer: " << fmtBoldEnd << ShowTagInInfoScreen(s.GetComposer());
info << fmtBold << "\nPerformer: " << fmtBoldEnd << ShowTagInInfoScreen(s.GetPerformer());
info << fmtBold << "\nDisc: " << fmtBoldEnd << ShowTagInInfoScreen(s.GetDisc());
info << fmtBold << "\nComment: " << fmtBoldEnd << ShowTagInInfoScreen(s.GetComment());
}
void ShowMessage(const char *format, ...)
{
if (messages_allowed)
{
time_of_statusbar_lock = time(NULL);
lock_statusbar_delay = Config.message_delay_time;
if (Config.statusbar_visibility)
block_statusbar_update = 1;
else
block_progressbar_update = 1;
wFooter->GotoXY(0, Config.statusbar_visibility);
wFooter->Bold(0);
va_list list;
va_start(list, format);
wmove(wFooter->Raw(), Config.statusbar_visibility, 0);
vw_printw(wFooter->Raw(), format, list);
wclrtoeol(wFooter->Raw());
va_end(list);
wFooter->Bold(1);
wFooter->Refresh();
}
}
Window &Statusbar()
{
wFooter->GotoXY(0, Config.statusbar_visibility);
wclrtoeol(wFooter->Raw());
return *wFooter;
}
const Buffer &ShowTag(const string &tag)
{
static Buffer result;
result.Clear();
if (tag.empty())
result << Config.empty_tags_color << Config.empty_tag << clEnd;
else
result << tag;
return result;
}
const basic_buffer<my_char_t> &ShowTagInInfoScreen(const string &tag)
{
# ifdef _UTF8
static WBuffer result;
result.Clear();
if (tag.empty())
result << Config.empty_tags_color << ToWString(Config.empty_tag) << clEnd;
else
result << ToWString(tag);
return result;
# else
return ShowTag(tag);
# endif
}
void Scroller(Window &w, const string &string, size_t width, size_t &pos)
{
std::basic_string<my_char_t> s = TO_WSTRING(string);
size_t len;
# ifdef _UTF8
len = Window::Length(s);
# else
len = s.length();
# endif
if (len > width)
{
# ifdef _UTF8
s += L" ** ";
# else
s += " ** ";
# endif
len = 0;
std::basic_string<my_char_t>::const_iterator b = s.begin(), e = s.end();
for (std::basic_string<my_char_t>::const_iterator it = b+pos; it != e && len < width; it++)
{
len += wcwidth(*it);
w << *it;
}
if (++pos >= s.length())
pos = 0;
for (; len < width; b++)
{
len += wcwidth(*b);
w << *b;
}
}
else
w << s;
}