/* 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 . */ #include "piscreen.h" #ifndef WINDOWS # include # include #else # include # 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 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 & ccv(cells[j]); PIVector & 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 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 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 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 tl(tiles()); piForeach (PIScreenTile * t, tl) if (t->name == name) return t; return 0; }