git-svn-id: svn://db.shs.com.ru/pip@28 12ceb7fc-bf1f-11e4-8940-5bc7170c53b5
499 lines
15 KiB
C++
499 lines
15 KiB
C++
/*
|
|
PIP - Platform Independent Primitives
|
|
Console output/input
|
|
Copyright (C) 2015 Ivan Pelipenko peri4ko@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 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "piscreen.h"
|
|
#ifndef WINDOWS
|
|
# include <sys/ioctl.h>
|
|
# include <fcntl.h>
|
|
#else
|
|
# include <wincon.h>
|
|
# ifndef COMMON_LVB_UNDERSCORE
|
|
# define COMMON_LVB_UNDERSCORE 0x8000
|
|
# endif
|
|
#endif
|
|
|
|
|
|
/** \class PIScreen
|
|
* \brief Console output class
|
|
* \details
|
|
* \section PIScreen_sec0 Synopsis
|
|
* This class provides output to console with automatic alignment and update.
|
|
* It supports tabs, keyboard listening, formats and colors.
|
|
*
|
|
* \section PIScreen_sec1 Layout
|
|
* %PIScreen works with variable pointers. You should add your variables with
|
|
* functions \a addVariable() which receives label name, pointer to variable
|
|
* and optional column and format. Columns count is dynamically increased if
|
|
* new column used. E.g. if you add variable to empty tab to column 3, columns
|
|
* count will be increased to 3, but two firsts columns will be empty. Each column
|
|
* filled from top to bottom, but you can add just string with function
|
|
* \a addString() or add empty line with function \a addEmptyLine(). Layout scheme:
|
|
* \image html piconsole_layout.png
|
|
*
|
|
* \section PIScreen_sec2 Keyboard usage
|
|
* %PIScreen should to be single in application. %PIScreen aggregate PIKbdListener
|
|
* which grab keyboard and automatic switch tabs by theirs bind keys. If there is no
|
|
* tab binded to pressed key external function "slot" will be called
|
|
*
|
|
**/
|
|
|
|
using namespace PIScreenTypes;
|
|
|
|
extern PIMutex __PICout_mutex__;
|
|
|
|
|
|
PRIVATE_DEFINITION_START(PIScreen::SystemConsole)
|
|
#ifdef WINDOWS
|
|
void * hOut;
|
|
CONSOLE_SCREEN_BUFFER_INFO sbi, csbi;
|
|
CONSOLE_CURSOR_INFO curinfo;
|
|
COORD ccoord, ulcoord, bs, bc;
|
|
SMALL_RECT srect;
|
|
WORD dattr;
|
|
DWORD smode, written;
|
|
PIVector<CHAR_INFO> chars;
|
|
#endif
|
|
PRIVATE_DEFINITION_END(PIScreen::SystemConsole)
|
|
|
|
|
|
PIScreen::SystemConsole::SystemConsole() {
|
|
width = height = pwidth = pheight = 0;
|
|
int w, h;
|
|
#ifdef WINDOWS
|
|
PRIVATE->ulcoord.X = 0;
|
|
PRIVATE->hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
GetConsoleScreenBufferInfo(PRIVATE->hOut, &PRIVATE->sbi);
|
|
PRIVATE->dattr = PRIVATE->sbi.wAttributes;
|
|
w = PRIVATE->sbi.srWindow.Right - PRIVATE->sbi.srWindow.Left;
|
|
h = PRIVATE->sbi.srWindow.Bottom - PRIVATE->sbi.srWindow.Top;
|
|
PRIVATE->ulcoord.Y = PRIVATE->sbi.srWindow.Top;
|
|
GetConsoleMode(PRIVATE->hOut, &PRIVATE->smode);
|
|
GetConsoleCursorInfo(PRIVATE->hOut, &PRIVATE->curinfo);
|
|
#else
|
|
winsize ws;
|
|
ioctl(0, TIOCGWINSZ, &ws);
|
|
w = ws.ws_col;
|
|
h = ws.ws_row;
|
|
#endif
|
|
resize(w, h);
|
|
}
|
|
|
|
|
|
PIScreen::SystemConsole::~SystemConsole() {
|
|
#ifdef WINDOWS
|
|
SetConsoleMode(PRIVATE->hOut, PRIVATE->smode);
|
|
SetConsoleTextAttribute(PRIVATE->hOut, PRIVATE->dattr);
|
|
#endif
|
|
}
|
|
|
|
|
|
void PIScreen::SystemConsole::begin() {
|
|
#ifdef WINDOWS
|
|
SetConsoleMode(PRIVATE->hOut, ENABLE_WRAP_AT_EOL_OUTPUT);
|
|
GetConsoleScreenBufferInfo(PRIVATE->hOut, &PRIVATE->sbi);
|
|
PRIVATE->bc.X = 0;
|
|
PRIVATE->bc.Y = 0;
|
|
#endif
|
|
clear();
|
|
hideCursor();
|
|
}
|
|
|
|
|
|
void PIScreen::SystemConsole::end() {
|
|
#ifdef WINDOWS
|
|
SetConsoleTextAttribute(PRIVATE->hOut, PRIVATE->dattr);
|
|
#else
|
|
printf("\e[0m");
|
|
#endif
|
|
moveTo(0, height);
|
|
showCursor();
|
|
}
|
|
|
|
|
|
void PIScreen::SystemConsole::prepare() {
|
|
int w, h;
|
|
#ifdef WINDOWS
|
|
GetConsoleScreenBufferInfo(PRIVATE->hOut, &PRIVATE->csbi);
|
|
w = PRIVATE->csbi.srWindow.Right - PRIVATE->csbi.srWindow.Left + 1;
|
|
h = PRIVATE->csbi.srWindow.Bottom - PRIVATE->csbi.srWindow.Top + 1;
|
|
#else
|
|
winsize ws;
|
|
ioctl(0, TIOCGWINSZ, &ws);
|
|
w = ws.ws_col;
|
|
h = ws.ws_row;
|
|
#endif
|
|
resize(w, h);
|
|
}
|
|
|
|
|
|
void PIScreen::SystemConsole::clear() {
|
|
for (int i = 0; i < cells.size_s(); ++i)
|
|
cells[i].fill(Cell());
|
|
}
|
|
|
|
|
|
void PIScreen::SystemConsole::resize(int w, int h) {
|
|
if (w == pwidth && h == pheight) return;
|
|
width = piMaxi(w, 0);
|
|
height = piMaxi(h, 0);
|
|
pwidth = width;
|
|
pheight = height;
|
|
cells.resize(height);
|
|
pcells.resize(height);
|
|
for (int i = 0; i < height; ++i) {
|
|
cells[i].resize(width);
|
|
pcells[i].resize(width, Cell(0));
|
|
}
|
|
#ifdef WINDOWS
|
|
PRIVATE->bs.X = width;
|
|
PRIVATE->bs.Y = height;
|
|
PRIVATE->sbi.srWindow = PRIVATE->csbi.srWindow;
|
|
PRIVATE->chars.resize(width * height);
|
|
#else
|
|
for (int i = 0; i < pcells.size_s(); ++i)
|
|
pcells[i].fill(Cell());
|
|
#endif
|
|
clear();
|
|
clearScreen();
|
|
}
|
|
|
|
|
|
void PIScreen::SystemConsole::print() {
|
|
#ifdef WINDOWS
|
|
static int cnt = 0;
|
|
for (int i = 0; i < width; ++i)
|
|
for (int j = 0; j < height; ++j) {
|
|
int k = j * width + i;
|
|
Cell & c(cells[j][i]);
|
|
PRIVATE->chars[k].Char.UnicodeChar = 0;
|
|
PRIVATE->chars[k].Char.AsciiChar = c.symbol.toAscii();
|
|
PRIVATE->chars[k].Attributes = attributes(c);
|
|
}
|
|
PRIVATE->srect = PRIVATE->sbi.srWindow;
|
|
WriteConsoleOutput(PRIVATE->hOut, PRIVATE->chars.data(), PRIVATE->bs, PRIVATE->bc, &PRIVATE->srect);
|
|
#else
|
|
PIString s;
|
|
int si = 0, sj = 0;
|
|
CellFormat prf(0xFFFFFFFF);
|
|
for (int j = 0; j < height; ++j) {
|
|
PIVector<Cell> & ccv(cells[j]);
|
|
PIVector<Cell> & pcv(pcells[j]);
|
|
for (int i = 0; i < width; ++i) {
|
|
Cell & cc(ccv[i]);
|
|
Cell & pc(pcv[i]);
|
|
if (cc != pc) {
|
|
if (s.isEmpty()) {
|
|
si = i;
|
|
sj = j;
|
|
}
|
|
if (prf != cc.format) {
|
|
prf = cc.format;
|
|
s += formatString(cc);
|
|
}
|
|
s += cc.symbol;
|
|
} else {
|
|
if (!s.isEmpty()) {
|
|
moveTo(si, sj);
|
|
printf("%s", s.data());
|
|
s.clear();
|
|
}
|
|
}
|
|
}
|
|
if (!s.isEmpty()) {
|
|
moveTo(si, sj);
|
|
printf("%s", s.data());
|
|
s.clear();
|
|
}
|
|
/*for (int i = 0; i < width; ++i) {
|
|
moveTo(i, j);
|
|
printf("%s", (formatString(cells[j][i]) + cells[j][i].symbol).data());
|
|
}*/
|
|
}
|
|
for (int i = 0; i < height; ++i)
|
|
pcells[i] = cells[i];
|
|
printf("\e[0m");
|
|
fflush(0);
|
|
#endif
|
|
}
|
|
|
|
|
|
#ifdef WINDOWS
|
|
#define FOREGROUND_MASK (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
|
|
#define BACKGROUND_MASK (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE)
|
|
WORD PIScreen::SystemConsole::attributes(const PIScreenTypes::Cell & c) {
|
|
WORD attr = PRIVATE->dattr;
|
|
switch (c.format.color_char) {
|
|
case Black: attr = (attr & ~FOREGROUND_MASK); break;
|
|
case Red: attr = (attr & ~FOREGROUND_MASK) | FOREGROUND_RED; break;
|
|
case Green: attr = (attr & ~FOREGROUND_MASK) | FOREGROUND_GREEN; break;
|
|
case Blue: attr = (attr & ~FOREGROUND_MASK) | FOREGROUND_BLUE; break;
|
|
case Cyan: attr = (attr & ~FOREGROUND_MASK) | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
|
|
case Magenta: attr = (attr & ~FOREGROUND_MASK) | FOREGROUND_RED | FOREGROUND_BLUE; break;
|
|
case Yellow: attr = (attr & ~FOREGROUND_MASK) | FOREGROUND_RED | FOREGROUND_GREEN; break;
|
|
case White: attr = attr | FOREGROUND_MASK; break;
|
|
}
|
|
switch (c.format.color_back) {
|
|
case Black: attr = (attr & ~BACKGROUND_MASK); break;
|
|
case Red: attr = (attr & ~BACKGROUND_MASK) | BACKGROUND_RED; break;
|
|
case Green: attr = (attr & ~BACKGROUND_MASK) | BACKGROUND_GREEN; break;
|
|
case Blue: attr = (attr & ~BACKGROUND_MASK) | BACKGROUND_BLUE; break;
|
|
case Cyan: attr = (attr & ~BACKGROUND_MASK) | BACKGROUND_GREEN | BACKGROUND_BLUE; break;
|
|
case Magenta: attr = (attr & ~BACKGROUND_MASK) | BACKGROUND_RED | BACKGROUND_BLUE; break;
|
|
case Yellow: attr = (attr & ~BACKGROUND_MASK) | BACKGROUND_RED | BACKGROUND_GREEN; break;
|
|
case White: attr = attr | BACKGROUND_MASK; break;
|
|
}
|
|
if (c.format.flags & Bold) attr |= FOREGROUND_INTENSITY;
|
|
else attr &= ~FOREGROUND_INTENSITY;
|
|
if (c.format.flags & Underline) attr |= COMMON_LVB_UNDERSCORE;
|
|
else attr &= ~COMMON_LVB_UNDERSCORE;
|
|
return attr;
|
|
}
|
|
#undef FOREGROUND_MASK
|
|
#undef BACKGROUND_MASK
|
|
void PIScreen::SystemConsole::getWinCurCoord() {GetConsoleScreenBufferInfo(PRIVATE->hOut, &PRIVATE->csbi); PRIVATE->ccoord = PRIVATE->csbi.dwCursorPosition;}
|
|
void PIScreen::SystemConsole::toUpperLeft() {SetConsoleCursorPosition(PRIVATE->hOut, PRIVATE->ulcoord);}
|
|
void PIScreen::SystemConsole::moveTo(int x, int y) {PRIVATE->ccoord.X = x; PRIVATE->ccoord.Y = PRIVATE->ulcoord.Y + y; SetConsoleCursorPosition(PRIVATE->hOut, PRIVATE->ccoord);}
|
|
void PIScreen::SystemConsole::clearScreen() {toUpperLeft(); FillConsoleOutputAttribute(PRIVATE->hOut, PRIVATE->dattr, width * (height + 1), PRIVATE->ulcoord, &PRIVATE->written);
|
|
FillConsoleOutputCharacter(PRIVATE->hOut, ' ', width * (height + 1), PRIVATE->ulcoord, &PRIVATE->written);}
|
|
void PIScreen::SystemConsole::clearScreenLower() {getWinCurCoord(); FillConsoleOutputAttribute(PRIVATE->hOut, PRIVATE->dattr, width * height - width * PRIVATE->ccoord.Y + PRIVATE->ccoord.X, PRIVATE->ccoord, &PRIVATE->written);
|
|
FillConsoleOutputCharacter(PRIVATE->hOut, ' ', width * height - width * PRIVATE->ccoord.Y + PRIVATE->ccoord.X, PRIVATE->ccoord, &PRIVATE->written);}
|
|
void PIScreen::SystemConsole::clearLine() {getWinCurCoord(); FillConsoleOutputAttribute(PRIVATE->hOut, PRIVATE->dattr, width - PRIVATE->ccoord.X, PRIVATE->ccoord, &PRIVATE->written);
|
|
FillConsoleOutputCharacter(PRIVATE->hOut, ' ', width - PRIVATE->ccoord.X, PRIVATE->ccoord, &PRIVATE->written);}
|
|
void PIScreen::SystemConsole::newLine() {getWinCurCoord(); PRIVATE->ccoord.X = 0; PRIVATE->ccoord.Y++; SetConsoleCursorPosition(PRIVATE->hOut, PRIVATE->ccoord);}
|
|
void PIScreen::SystemConsole::hideCursor() {PRIVATE->curinfo.bVisible = false; SetConsoleCursorInfo(PRIVATE->hOut, &PRIVATE->curinfo);}
|
|
void PIScreen::SystemConsole::showCursor() {PRIVATE->curinfo.bVisible = true; SetConsoleCursorInfo(PRIVATE->hOut, &PRIVATE->curinfo);}
|
|
#endif
|
|
|
|
|
|
#ifndef WINDOWS
|
|
PIString PIScreen::SystemConsole::formatString(const PIScreenTypes::Cell & c) {
|
|
PIString ts("\e[0");
|
|
switch (c.format.color_char) {
|
|
case Black: ts += ";30"; break;
|
|
case Red: ts += ";31"; break;
|
|
case Green: ts += ";32"; break;
|
|
case Blue: ts += ";34"; break;
|
|
case Cyan: ts += ";36"; break;
|
|
case Magenta: ts += ";35"; break;
|
|
case Yellow: ts += ";33"; break;
|
|
case White: ts += ";37"; break;
|
|
}
|
|
if (c.format.flags & Bold) ts += ";1";
|
|
if (c.format.flags & Blink) ts += ";5";
|
|
if (c.format.flags & Underline) ts += ";4";
|
|
switch (c.format.color_back) {
|
|
case Black: ts += ";40"; break;
|
|
case Red: ts += ";41"; break;
|
|
case Green: ts += ";42"; break;
|
|
case Blue: ts += ";44"; break;
|
|
case Cyan: ts += ";46"; break;
|
|
case Magenta: ts += ";45"; break;
|
|
case Yellow: ts += ";43"; break;
|
|
case White: ts += ";47"; break;
|
|
}
|
|
return ts + "m";
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
PIScreen::PIScreen(bool startNow, PIKbdListener::KBFunc slot): PIThread(), drawer_(console.cells), root("rootTile") {
|
|
setName("screen");
|
|
setPriority(piLow);
|
|
needLockRun(true);
|
|
ret_func = slot;
|
|
tile_focus = tile_dialog = 0;
|
|
root.screen = this;
|
|
listener = new PIKbdListener(key_eventS, this);
|
|
if (startNow) start();
|
|
}
|
|
|
|
|
|
PIScreen::~PIScreen() {
|
|
if (isRunning())
|
|
stop();
|
|
delete listener;
|
|
}
|
|
|
|
|
|
void PIScreen::key_event(PIKbdListener::KeyEvent key) {
|
|
/** DEBUG
|
|
if (ret_func != 0) ret_func(key, t);
|
|
keyPressed(key, t);
|
|
return;
|
|
*/
|
|
PIScreenTile * rtile = rootTile();
|
|
if (tile_dialog)
|
|
rtile = tile_dialog;
|
|
bool used = nextFocus(rtile, key);
|
|
if (used) return;
|
|
if (!used && tile_focus) {
|
|
if (tile_focus->visible) {
|
|
if (tile_focus->keyEvent(key))
|
|
return;
|
|
}
|
|
}
|
|
if (ret_func != 0) ret_func(key, data_);
|
|
keyPressed(key, data_);
|
|
}
|
|
|
|
|
|
bool PIScreen::nextFocus(PIScreenTile * rt, PIKbdListener::KeyEvent key) {
|
|
PIVector<PIScreenTile*> vtl = rt->children(true), ftl;
|
|
piForeach (PIScreenTile * t, vtl) {
|
|
if (t->focus_flags[CanHasFocus])
|
|
ftl << t;
|
|
}
|
|
int ind = -1;
|
|
for (int i = 0; i < ftl.size_s(); ++i)
|
|
if (ftl[i] == tile_focus) {
|
|
ind = i;
|
|
break;
|
|
}
|
|
if (ind < 0)
|
|
tile_focus = 0;
|
|
if (ftl.isEmpty())
|
|
tile_focus = 0;
|
|
else {
|
|
if (tile_focus)
|
|
if (!tile_focus->visible)
|
|
tile_focus = 0;
|
|
int next = tile_focus ? 0 : 1;
|
|
if (tile_focus) {
|
|
if (tile_focus->focus_flags[NextByTab] && key.key == PIKbdListener::Tab)
|
|
next = 1;
|
|
if (tile_focus->focus_flags[NextByArrows]) {
|
|
if (key.key == PIKbdListener::LeftArrow) next = -1;
|
|
if (key.key == PIKbdListener::RightArrow) next = 1;
|
|
}
|
|
}
|
|
//piCout << ftl.size() << ind << next;
|
|
if (next != 0) {
|
|
PIVector<PIScreenTile*> tl = rt->children();
|
|
piForeach (PIScreenTile * t, tl)
|
|
t->has_focus = false;
|
|
if (!ftl.isEmpty()) {
|
|
ind += next;
|
|
if (ind >= ftl.size_s()) ind = 0;
|
|
if (ind < 0) ind = ftl.size_s() - 1;
|
|
tile_focus = ftl[ind];
|
|
tile_focus->has_focus = true;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void PIScreen::tileEventInternal(PIScreenTile * t, TileEvent e) {
|
|
tileEvent(t, e);
|
|
}
|
|
|
|
|
|
void PIScreen::tileRemovedInternal(PIScreenTile * t) {
|
|
if (tile_dialog == t)
|
|
tile_dialog = 0;
|
|
}
|
|
|
|
|
|
void PIScreen::tileSetFocusInternal(PIScreenTile * t) {
|
|
PIScreenTile * rt = rootTile();
|
|
if (tile_dialog)
|
|
rt = tile_dialog;
|
|
PIVector<PIScreenTile*> tl = rt->children(), ftl;
|
|
piForeach (PIScreenTile * i, tl)
|
|
i->has_focus = false;
|
|
tile_focus = t;
|
|
if (!tile_focus) return;
|
|
if (tile_focus->focus_flags[CanHasFocus])
|
|
tile_focus->has_focus = true;
|
|
}
|
|
|
|
|
|
void PIScreen::setDialogTile(PIScreenTile * t) {
|
|
tile_dialog = t;
|
|
if (!tile_dialog) {
|
|
nextFocus(&root);
|
|
return;
|
|
}
|
|
tile_dialog->setScreen(this);
|
|
tile_dialog->parent = 0;
|
|
nextFocus(tile_dialog);
|
|
}
|
|
|
|
|
|
void PIScreen::waitForFinish() {
|
|
WAIT_FOR_EXIT
|
|
stop();
|
|
}
|
|
|
|
|
|
void PIScreen::stop(bool clear) {
|
|
PIThread::stop(true);
|
|
if (clear) console.clearScreen();
|
|
#ifndef WINDOWS
|
|
fflush(0);
|
|
#endif
|
|
}
|
|
|
|
|
|
void PIScreen::begin() {
|
|
nextFocus(&root);
|
|
console.begin();
|
|
}
|
|
|
|
|
|
void PIScreen::run() {
|
|
console.prepare();
|
|
root.width = drawer_.width = console.width;
|
|
root.height = drawer_.height = console.height;
|
|
root.layout();
|
|
root.drawEventInternal(&drawer_);
|
|
if (tile_dialog) {
|
|
int sw(0), sh(0);
|
|
tile_dialog->sizeHint(sw, sh);
|
|
sw = piClampi(sw, tile_dialog->minimumWidth, tile_dialog->maximumWidth);
|
|
sh = piClampi(sh, tile_dialog->minimumHeight, tile_dialog->maximumHeight);
|
|
tile_dialog->x = (console.width - sw) / 2;
|
|
tile_dialog->y = (console.height - sh) / 2;
|
|
tile_dialog->width = sw;
|
|
tile_dialog->height = sh;
|
|
tile_dialog->layout();
|
|
tile_dialog->drawEventInternal(&drawer_);
|
|
}
|
|
console.print();
|
|
}
|
|
|
|
|
|
void PIScreen::end() {
|
|
console.end();
|
|
}
|
|
|
|
|
|
PIScreenTile * PIScreen::tileByName(const PIString & name) {
|
|
PIVector<PIScreenTile*> tl(tiles());
|
|
piForeach (PIScreenTile * t, tl)
|
|
if (t->name == name)
|
|
return t;
|
|
return 0;
|
|
}
|
|
|