/*
PIP - Platform Independent Primitives
Universal output to console class
Ivan Pelipenko peri4ko@yandex.ru
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see .
*/
#include "picout.h"
#include "pibytearray.h"
#include "piincludes_p.h"
#include "piobject.h"
#include "pistack.h"
#include "pistring_std.h"
#ifdef HAS_LOCALE
# include
# include
#endif
#ifdef WINDOWS
# include
# include
# include
# define COMMON_LVB_UNDERSCORE 0x8000
#endif
//! \~\class PICout picout.h
//! \~\details
//! \~english \section PICout_sec0 Synopsis
//! \~russian \section PICout_sec0 Краткий обзор
//! \~english
//! This class provide many stream operators for output with some features.
//! Output to %PICout is thread-sequential, i.e. doesn`t mixed from parallel
//! threads.
//!
//! \~russian
//! Данный класс предоставляет множество операторов для вывода в консоль.
//! Вывод в %PICout потоково-последовательный, т.е. не смешивается из параллельных
//! потоков.
//!
//! \~english \section PICout_sec1 Features
//! \~russian \section PICout_sec1 Особенности
//! \~english
//! * support text formatting (color, style)
//! * insertion spaces between entries
//! * insertion new line at the end of output
//! * strings are quoted
//! * custom output operator can be easily written
//! * can outpur to console, internal buffer or both
//!
//! \~russian
//! * поддержка форматирования (цвет, стиль)
//! * вставка пробелов между выводами
//! * вставка новой строки после последнего вывода
//! * строки обрамляются кавычками
//! * легко создавать сови операторы вывода
//! * может выводить в консоль, внутренний буфер или в оба места
//!
//! \~english \section PICout_ex0 Usage
//! \~russian \section PICout_ex0 Использование
//! \~\snippet picout.cpp 0
//!
//! \~english \section PICout_ex1 Writing your own output operator
//! \~russian \section PICout_ex1 Создание своего оператора вывода
//! \~\snippet picout.cpp own
//!
//! \addtogroup Core
//! \{
//! \~\class PICout::Notifier picout.h
//! \~\brief
//! \~english Class for emit notifications of PICout
//! \~russian Класс для посылки событий от PICout
//!
//! \~english \section PICoutNotifier_sec0 Synopsis
//! \~russian \section PICoutNotifier_sec0 Краткий обзор
//! \~english
//! This class used as PICout events emitter. When
//! PICout constructs with external PIString* buffer
//! and some ID, last copy of this PICout on delete
//! emit event "finished()" on object Notifier::object().
//! Sample:
//!
//! \~russian
//! Этот класс используется как источник событий PICout.
//! Когда PICout сконструирован с внешним буфером PIString*
//! и каким-то ID, последняя копия этого PICout при уничтожении
//! посылает событие "finished()" у объекта Notifier::object().
//! Пример:
//!
//! \~\snippet picout.cpp notifier
//!
//! \}
class NotifierObject: public PIObject {
PIOBJECT(NotifierObject)
public:
NotifierObject() {}
EVENT2(finished, int, id, PIString *, buffer);
};
PICout::Notifier::Notifier() {
o = new NotifierObject();
}
PICout::Notifier * PICout::Notifier::instance() {
static PICout::Notifier * ret = new PICout::Notifier();
return ret;
}
PIObject * PICout::Notifier::object() {
return instance()->o;
}
using namespace PICoutManipulators;
PIMutex & PICout::__mutex__() {
static PIMutex * ret = new PIMutex();
return *ret;
}
PIString & PICout::__string__() {
static PIString * ret = new PIString();
return *ret;
}
PICout::OutputDevices PICout::devs = PICout::Console;
PRIVATE_DEFINITION_START(PICout)
PIStack cos_;
#ifdef WINDOWS
static void * hOut;
static WORD dattr;
static DWORD smode;
#endif
PRIVATE_DEFINITION_END(PICout)
#ifdef WINDOWS
void * PICout::__Private__::hOut = 0;
WORD PICout::__Private__::dattr = 0;
DWORD PICout::__Private__::smode = 0;
#endif
std::ostream & getStdStream(PICoutStdStream s) {
switch (s) {
case PICoutStdStream::StdOut: return std::cout;
case PICoutStdStream::StdErr: return std::cerr;
default: break;
}
return std::cout;
}
std::wostream & getStdWStream(PICoutStdStream s) {
switch (s) {
case PICoutStdStream::StdOut: return std::wcout;
case PICoutStdStream::StdErr: return std::wcerr;
default: break;
}
return std::wcout;
}
PICout::PICout(int controls, PICoutStdStream stream): ctrl_(controls), stream_(stream) {
init();
}
PICout::PICout(bool active, PICoutStdStream stream): actve_(active), stream_(stream) {
if (actve_) init();
}
PICout::PICout(const PICout & other)
: first_out_(other.first_out_)
, is_copy_(true)
, actve_(other.actve_)
, int_base_(other.int_base_)
, win_attr_(other.win_attr_)
, id_(other.id_)
, buffer_(other.buffer_)
, ctrl_(other.ctrl_)
, stream_(other.stream_) {}
PICout::~PICout() {
if (!actve_) return;
if (format_changed_) applyFormat(PICoutManipulators::Default);
if (is_copy_) return;
newLine();
if ((ctrl_ & NoLock) != NoLock) {
PICout::__mutex__().unlock();
}
if (buffer_) {
if (id_ >= 0) ((NotifierObject *)Notifier::object())->finished(id_, buffer_);
} else {
getStdStream(stream_).flush();
}
}
PICout & PICout::operator<<(PICoutAction v) {
if (!actve_) return *this;
#ifdef WINDOWS
CONSOLE_SCREEN_BUFFER_INFO sbi;
COORD coord;
CONSOLE_CURSOR_INFO curinfo;
#endif
switch (v) {
case PICoutManipulators::Flush:
if (!buffer_ && isOutputDeviceActive(Console)) {
getStdStream(stream_) << std::flush;
}
break;
case PICoutManipulators::Backspace:
if (isOutputDeviceActive(Console)) {
#ifdef WINDOWS
GetConsoleScreenBufferInfo(__Private__::hOut, &sbi);
coord = sbi.dwCursorPosition;
coord.X = piMax(0, int(coord.X) - 1);
SetConsoleCursorPosition(__Private__::hOut, coord);
printf(" ");
SetConsoleCursorPosition(__Private__::hOut, coord);
#else
printf("\e[1D \e[1D");
#endif
}
break;
case PICoutManipulators::ShowCursor:
if (isOutputDeviceActive(Console)) {
#ifdef WINDOWS
GetConsoleCursorInfo(__Private__::hOut, &curinfo);
curinfo.bVisible = true;
SetConsoleCursorInfo(__Private__::hOut, &curinfo);
#else
printf("\e[?25h");
#endif
}
break;
case PICoutManipulators::HideCursor:
if (isOutputDeviceActive(Console)) {
#ifdef WINDOWS
GetConsoleCursorInfo(__Private__::hOut, &curinfo);
curinfo.bVisible = false;
SetConsoleCursorInfo(__Private__::hOut, &curinfo);
#else
printf("\e[?25l");
#endif
}
break;
case PICoutManipulators::ClearLine:
if (isOutputDeviceActive(Console)) {
#ifdef WINDOWS
GetConsoleScreenBufferInfo(__Private__::hOut, &sbi);
coord = sbi.dwCursorPosition;
int dx = coord.X;
coord.X = 0;
SetConsoleCursorPosition(__Private__::hOut, coord);
if (dx > 0) {
char * line = new char[dx + 1];
memset(line, ' ', dx);
line[dx] = 0;
printf("%s", line);
delete[] line;
}
SetConsoleCursorPosition(__Private__::hOut, coord);
#else
printf("\e[0G\e[K");
#endif
}
break;
case PICoutManipulators::ClearScreen:
if (isOutputDeviceActive(Console)) {
#ifdef WINDOWS
/// TODO : wondows ClearScreen !!!
#else
printf("\e[H\e[J");
#endif
}
break;
case PICoutManipulators::SaveContol: saveControls(); break;
case PICoutManipulators::RestoreControl: restoreControls(); break;
default: break;
};
return *this;
}
PICout & PICout::setControl(PICoutManipulators::PICoutControl c, bool on) {
ctrl_.setFlag(c, on);
return *this;
}
PICout & PICout::setControls(PICoutManipulators::PICoutControls c) {
ctrl_ = c;
return *this;
}
PICout & PICout::saveAndSetControls(PICoutManipulators::PICoutControls c) {
saveControls();
ctrl_ = c;
return *this;
}
PICout & PICout::operator<<(PICoutManipulators::PICoutFormat v) {
switch (v) {
case PICoutManipulators::Bin: int_base_ = 2; break;
case PICoutManipulators::Oct: int_base_ = 8; break;
case PICoutManipulators::Dec: int_base_ = 10; break;
case PICoutManipulators::Hex: int_base_ = 16; break;
default: applyFormat(v);
};
return *this;
}
PICout & PICout::operator<<(PIFlags v) {
if (v[PICoutManipulators::Bin]) int_base_ = 2;
if (v[PICoutManipulators::Oct]) int_base_ = 8;
if (v[PICoutManipulators::Dec]) int_base_ = 10;
if (v[PICoutManipulators::Hex]) int_base_ = 16;
if (v[PICoutManipulators::Bold]) applyFormat(PICoutManipulators::Bold);
if (v[PICoutManipulators::Faint]) applyFormat(PICoutManipulators::Faint);
if (v[PICoutManipulators::Italic]) applyFormat(PICoutManipulators::Italic);
if (v[PICoutManipulators::Underline]) applyFormat(PICoutManipulators::Underline);
if (v[PICoutManipulators::Blink]) applyFormat(PICoutManipulators::Blink);
if (v[PICoutManipulators::Black]) applyFormat(PICoutManipulators::Black);
if (v[PICoutManipulators::Red]) applyFormat(PICoutManipulators::Red);
if (v[PICoutManipulators::Green]) applyFormat(PICoutManipulators::Green);
if (v[PICoutManipulators::Blue]) applyFormat(PICoutManipulators::Blue);
if (v[PICoutManipulators::Yellow]) applyFormat(PICoutManipulators::Yellow);
if (v[PICoutManipulators::Magenta]) applyFormat(PICoutManipulators::Magenta);
if (v[PICoutManipulators::Cyan]) applyFormat(PICoutManipulators::Cyan);
if (v[PICoutManipulators::White]) applyFormat(PICoutManipulators::White);
if (v[PICoutManipulators::BackBlack]) applyFormat(PICoutManipulators::BackBlack);
if (v[PICoutManipulators::BackRed]) applyFormat(PICoutManipulators::BackRed);
if (v[PICoutManipulators::BackGreen]) applyFormat(PICoutManipulators::BackGreen);
if (v[PICoutManipulators::BackBlue]) applyFormat(PICoutManipulators::BackBlue);
if (v[PICoutManipulators::BackYellow]) applyFormat(PICoutManipulators::BackYellow);
if (v[PICoutManipulators::BackMagenta]) applyFormat(PICoutManipulators::BackMagenta);
if (v[PICoutManipulators::BackCyan]) applyFormat(PICoutManipulators::BackCyan);
if (v[PICoutManipulators::BackWhite]) applyFormat(PICoutManipulators::BackWhite);
if (v[PICoutManipulators::Default]) applyFormat(PICoutManipulators::Default);
return *this;
}
void PICout::stdoutPIString(const PIString & str, PICoutStdStream s) {
#ifdef HAS_LOCALE
std::wstring_convert, char16_t> utf8conv;
getStdStream(s) << utf8conv.to_bytes((char16_t *)&(const_cast(str).front()),
(char16_t *)&(const_cast(str).front()) + str.size());
#else
for (PIChar c: str)
getStdWStream(s).put(c.toWChar());
#endif
}
PICout & PICout::write(const char * str, int len) {
if (!actve_ || !str) return *this;
if (buffer_) {
buffer_->append(PIString(str, len));
} else {
if (isOutputDeviceActive(Console)) getStdStream(stream_).write(str, len);
if (isOutputDeviceActive(Buffer)) PICout::__string__().append(PIString(str, len));
}
return *this;
}
PICout & PICout::write(const PIString & s) {
if (!actve_) return *this;
if (buffer_) {
buffer_->append(s);
} else {
if (isOutputDeviceActive(Console)) stdoutPIString(s, stream_);
if (isOutputDeviceActive(Buffer)) PICout::__string__().append(s);
}
return *this;
}
void PICout::writeChar(char c) {
if (buffer_) {
buffer_->append(c);
} else {
if (isOutputDeviceActive(Console)) getStdStream(stream_) << c;
if (isOutputDeviceActive(Buffer)) PICout::__string__().append(c);
}
}
#define PIINTCOUT(v) \
{ \
if (!actve_) return *this; \
space(); \
if (int_base_ == 10) { \
if (buffer_) { \
(*buffer_) += PIString::fromNumber(v); \
} else { \
if (isOutputDeviceActive(Console)) getStdStream(stream_) << (v); \
if (isOutputDeviceActive(Buffer)) PICout::__string__() += PIString::fromNumber(v); \
} \
} else \
write(PIString::fromNumber(v, int_base_)); \
return *this; \
}
#define PIFLOATCOUT(v) \
{ \
if (buffer_) { \
(*buffer_) += PIString::fromNumber(v, 'g'); \
} else { \
if (isOutputDeviceActive(Console)) getStdStream(stream_) << (v); \
if (isOutputDeviceActive(Buffer)) PICout::__string__() += PIString::fromNumber(v, 'g'); \
} \
} \
return *this;
PICout & PICout::operator<<(const PIString & v) {
space();
quote();
write(v);
quote();
return *this;
}
PICout & PICout::operator<<(const char * v) {
if (!actve_ || !v) return *this;
if (v[0] == '\0') return *this;
space();
quote();
write(v);
quote();
return *this;
}
PICout & PICout::operator<<(bool v) {
if (!actve_) return *this;
space();
if (v)
write("true");
else
write("false");
return *this;
}
PICout & PICout::operator<<(char v) {
if (!actve_) return *this;
space();
write(v);
return *this;
}
PICout & PICout::operator<<(uchar v){PIINTCOUT(ushort(v))}
PICout & PICout::operator<<(short int v){PIINTCOUT(v)}
PICout & PICout::operator<<(ushort v){PIINTCOUT(v)}
PICout & PICout::operator<<(int v){PIINTCOUT(v)}
PICout & PICout::operator<<(uint v){PIINTCOUT(v)}
PICout & PICout::operator<<(long v){PIINTCOUT(v)}
PICout & PICout::operator<<(ulong v){PIINTCOUT(v)}
PICout & PICout::operator<<(llong v){PIINTCOUT(v)}
PICout & PICout::operator<<(ullong v){PIINTCOUT(v)}
PICout & PICout::operator<<(float v) {
if (!actve_) return *this;
space();
PIFLOATCOUT(v)
}
PICout & PICout::operator<<(double v) {
if (!actve_) return *this;
space();
PIFLOATCOUT(v)
}
PICout & PICout::operator<<(ldouble v) {
if (!actve_) return *this;
space();
PIFLOATCOUT(v)
}
PICout & PICout::operator<<(const void * v) {
if (!actve_) return *this;
space();
write("0x" + PIString::fromNumber(ullong(v), 16));
return *this;
}
PICout & PICout::operator<<(const PIObject * v) {
if (!actve_) return *this;
space();
if (v == 0)
write("PIObject*(0x0)");
else {
write(v->className());
write("*(0x" + PIString::fromNumber(ullong(v), 16) + ", \"" + v->name() + "\")");
}
return *this;
}
PICout & PICout::operator<<(PICoutSpecialChar v) {
if (!actve_) return *this;
switch (v) {
case Null: writeChar(char(0)); break;
case NewLine:
first_out_ = true;
writeChar('\n');
break;
case Tab: writeChar('\t'); break;
case Esc:
#ifdef CC_VC
writeChar(char(27));
#else
writeChar('\e');
#endif
break;
case Quote: writeChar('"'); break;
};
return *this;
}
PICout & PICout::saveControls() {
if (!actve_) return *this;
PRIVATE->cos_.push(ctrl_);
return *this;
}
PICout & PICout::restoreControls() {
if (!actve_) return *this;
if (!PRIVATE->cos_.isEmpty()) {
ctrl_ = PRIVATE->cos_.top();
PRIVATE->cos_.pop();
}
return *this;
}
//! \details
//! \~english
//! If it is not a first output and control \a AddSpaces is set space character is put
//! \~russian
//! Добавляет пробел если это не первый вывод и установлен флаг \a AddSpaces
//! \~\sa \a quote(), \a newLine()
PICout & PICout::space() {
if (!actve_) return *this;
if (!first_out_ && ctrl_[AddSpaces]) {
writeChar(' ');
}
first_out_ = false;
return *this;
}
//! \details
//! \~english
//! If control \a AddQuotes is set quote character is put
//! \~russian
//! Добавляет кавычки если установлен флаг \a AddQuotes
//! \~\sa \a space(), \a newLine()
PICout & PICout::quote() {
if (!actve_) return *this;
if (ctrl_[AddQuotes]) {
writeChar('"');
}
first_out_ = false;
return *this;
}
//! \details
//! \~english
//! If control \a AddNewLine is set new line character is put
//! \~russian
//! Добавляет новую строку если установлен флаг \a AddNewLine
//! \~\sa \a space(), \a quote()
PICout & PICout::newLine() {
if (!actve_) return *this;
if (ctrl_[AddNewLine]) {
writeChar('\n');
}
first_out_ = false;
return *this;
}
PICout & PICout::write(char c) {
if (!actve_) return *this;
writeChar(c);
return *this;
}
PICout & PICout::write(const char * str) {
if (!actve_ || !str) return *this;
return write(str, strlen(str));
}
void PICout::init() {
#ifdef WINDOWS
if (__Private__::hOut == 0) {
__Private__::hOut = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO sbi;
GetConsoleScreenBufferInfo(__Private__::hOut, &sbi);
__Private__::dattr = sbi.wAttributes;
}
win_attr_ = __Private__::dattr;
#endif
if ((ctrl_ & NoLock) != NoLock) {
PICout::__mutex__().lock();
}
}
void PICout::applyFormat(PICoutFormat f) {
if (!actve_) return;
if (buffer_ || !isOutputDeviceActive(Console)) return;
format_changed_ = true;
#ifdef WINDOWS
static int mask_fore = ~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
static int mask_back = ~(BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE);
switch (f) {
case Bin:
case Oct:
case Dec:
case Hex: break;
case PICoutManipulators::Bold: win_attr_ |= FOREGROUND_INTENSITY; break;
case PICoutManipulators::Underline: win_attr_ |= COMMON_LVB_UNDERSCORE; break;
case PICoutManipulators::Black: win_attr_ = (win_attr_ & mask_fore); break;
case PICoutManipulators::Red: win_attr_ = (win_attr_ & mask_fore) | FOREGROUND_RED; break;
case PICoutManipulators::Green: win_attr_ = (win_attr_ & mask_fore) | FOREGROUND_GREEN; break;
case PICoutManipulators::Blue: win_attr_ = (win_attr_ & mask_fore) | FOREGROUND_BLUE; break;
case PICoutManipulators::Yellow: win_attr_ = (win_attr_ & mask_fore) | FOREGROUND_RED | FOREGROUND_GREEN; break;
case PICoutManipulators::Magenta: win_attr_ = (win_attr_ & mask_fore) | FOREGROUND_RED | FOREGROUND_BLUE; break;
case PICoutManipulators::Cyan: win_attr_ = (win_attr_ & mask_fore) | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
case PICoutManipulators::White: win_attr_ = (win_attr_ & mask_fore) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
case PICoutManipulators::BackBlack: win_attr_ = (win_attr_ & mask_back); break;
case PICoutManipulators::BackRed: win_attr_ = (win_attr_ & mask_back) | BACKGROUND_RED; break;
case PICoutManipulators::BackGreen: win_attr_ = (win_attr_ & mask_back) | BACKGROUND_GREEN; break;
case PICoutManipulators::BackBlue: win_attr_ = (win_attr_ & mask_back) | BACKGROUND_BLUE; break;
case PICoutManipulators::BackYellow: win_attr_ = (win_attr_ & mask_back) | BACKGROUND_RED | BACKGROUND_GREEN; break;
case PICoutManipulators::BackMagenta: win_attr_ = (win_attr_ & mask_back) | BACKGROUND_RED | BACKGROUND_BLUE; break;
case PICoutManipulators::BackCyan: win_attr_ = (win_attr_ & mask_back) | BACKGROUND_GREEN | BACKGROUND_BLUE; break;
case PICoutManipulators::BackWhite: win_attr_ = (win_attr_ & mask_back) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; break;
case PICoutManipulators::Default: win_attr_ = __Private__::dattr; break;
default: break;
}
SetConsoleTextAttribute(__Private__::hOut, win_attr_);
#else
switch (f) {
case Bin:
case Oct:
case Dec:
case Hex: break;
case PICoutManipulators::Bold: printf("\e[1m"); break;
case PICoutManipulators::Faint: printf("\e[2m"); break;
case PICoutManipulators::Italic: printf("\e[3m"); break;
case PICoutManipulators::Underline: printf("\e[4m"); break;
case PICoutManipulators::Blink: printf("\e[5m"); break;
case PICoutManipulators::Black: printf("\e[30m"); break;
case PICoutManipulators::Red: printf("\e[31m"); break;
case PICoutManipulators::Green: printf("\e[32m"); break;
case PICoutManipulators::Blue: printf("\e[34m"); break;
case PICoutManipulators::Yellow: printf("\e[33m"); break;
case PICoutManipulators::Magenta: printf("\e[35m"); break;
case PICoutManipulators::Cyan: printf("\e[36m"); break;
case PICoutManipulators::White: printf("\e[37m"); break;
case PICoutManipulators::BackBlack: printf("\e[40m"); break;
case PICoutManipulators::BackRed: printf("\e[41m"); break;
case PICoutManipulators::BackGreen: printf("\e[42m"); break;
case PICoutManipulators::BackBlue: printf("\e[44m"); break;
case PICoutManipulators::BackYellow: printf("\e[43m"); break;
case PICoutManipulators::BackMagenta: printf("\e[45m"); break;
case PICoutManipulators::BackCyan: printf("\e[46m"); break;
case PICoutManipulators::BackWhite: printf("\e[47m"); break;
case PICoutManipulators::Default: printf("\e[0m"); break;
default: break;
}
#endif
}
PIString PICout::getBuffer() {
PIMutexLocker ml(PICout::__mutex__());
PIString ret = PICout::__string__();
return ret;
}
PIString PICout::getBufferAndClear() {
PIMutexLocker ml(PICout::__mutex__());
PIString ret = PICout::__string__();
PICout::__string__().clear();
return ret;
}
void PICout::clearBuffer() {
PIMutexLocker ml(PICout::__mutex__());
PICout::__string__().clear();
}
bool PICout::setOutputDevice(PICout::OutputDevice d, bool on) {
bool ret = devs[d];
devs.setFlag(d, on);
return ret;
}
void PICout::setOutputDevices(PICout::OutputDevices d) {
devs = d;
}
bool PICout::isOutputDeviceActive(PICout::OutputDevice d) {
return devs[d];
}
PICout PICout::withExternalBuffer(PIString * buffer, PIFlags controls) {
return withExternalBufferAndID(buffer, -1, controls);
}
PICout PICout::withExternalBufferAndID(PIString * buffer, int id, PIFlags controls) {
PICout c(controls);
c.buffer_ = buffer;
c.id_ = id;
return c;
}
int PICout::registerExternalBufferID() {
return Notifier::instance()->new_id.fetch_add(1);
}