/* PIP - Platform Independent Primitives Console output/input Copyright (C) 2020 Ivan Pelipenko peri4ko@yandex.ru 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" #include "piincludes_p.h" #ifndef WINDOWS # include # include # include #else # include # ifndef COMMON_LVB_UNDERSCORE # define COMMON_LVB_UNDERSCORE 0x8000 # endif #endif using namespace PIScreenTypes; 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; mouse_x = mouse_y = -1; 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 # ifdef FREERTOS w = 80; h = 24; # else winsize ws; ioctl(0, TIOCGWINSZ, &ws); w = ws.ws_col; h = ws.ws_row; # endif #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 # ifdef FREERTOS w = 80; h = 24; # else winsize ws; ioctl(0, TIOCGWINSZ, &ws); w = ws.ws_col; h = ws.ws_row; # endif #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->sbi.srWindow = PRIVATE->csbi.srWindow; PRIVATE->chars.resize(width * height); #endif for (int i = 0; i < pcells.size_s(); ++i) pcells[i].fill(Cell()); clear(); clearScreen(); } void PIScreen::SystemConsole::print() { if (mouse_x >= 0 && mouse_x < width && mouse_y >= 0 && mouse_y < height) { ///cells[mouse_y][mouse_x].format.flags ^= Inverse; } #ifdef WINDOWS //static int cnt = 0; PRIVATE->srect = PRIVATE->sbi.srWindow; int dx0 = -1, dx1 = -1, dy0 = -1, dy1 = -1; for (int j = 0; j < height; ++j) { PIVector & ccv(cells[j]); PIVector & pcv(pcells[j]); for (int i = 0; i < width; ++i) if (ccv[i] != pcv[i]) { if (dx0 < 0) { dx0 = dx1 = i; dy0 = dy1 = j; } else { dx0 = piMini(dx0, i); dx1 = piMaxi(dx1, i); dy0 = piMini(dy0, j); dy1 = piMaxi(dy1, j); } } } if (dx0 < 0) return; int dw = dx1 - dx0 + 1, dh = dy1 - dy0 + 1; for (int i = 0; i < dw; ++i) for (int j = 0; j < dh; ++j) { int k = j * dw + i; Cell & c(cells[j + dy0][i + dx0]); PRIVATE->chars[k].Char.UnicodeChar = 0; PRIVATE->chars[k].Char.AsciiChar = c.symbol.toConsole1Byte(); //PRIVATE->chars[k].Char.UnicodeChar = c.symbol.toInt(); PRIVATE->chars[k].Attributes = attributes(c); } //PRIVATE->bc.X = dx0; //PRIVATE->bc.Y = dy0; //piCout << "draw" << dw << dh; PRIVATE->bs.X = dw; PRIVATE->bs.Y = dh; PRIVATE->srect.Left += dx0; PRIVATE->srect.Top += dy0; PRIVATE->srect.Right -= width - dx1 - 1; PRIVATE->srect.Bottom -= height - dy1 - 1; WriteConsoleOutput(PRIVATE->hOut, PRIVATE->chars.data(), PRIVATE->bs, PRIVATE->bc, &PRIVATE->srect); #else PIString s; int si = 0, sj = 0; CellFormat prf(0xFFFF); 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()); }*/ } printf("\e[0m"); fflush(0); #endif pcells = cells; } #ifdef WINDOWS #define FOREGROUND_MASK (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE) #define BACKGROUND_MASK (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE) ushort PIScreen::SystemConsole::attributes(const PIScreenTypes::Cell & c) { WORD attr = PRIVATE->dattr; 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; 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 & Inverse) == Inverse) { uchar f = attr & 0xFF; attr &= 0xFFFFFF00; f = (f << 4) | (f >> 4); attr |= f; } 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::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); } #else // 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; } 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; } if ((c.format.flags & Bold) == Bold) ts += ";1"; if ((c.format.flags & Underline) == Underline) ts += ";4"; if ((c.format.flags & Blink) == Blink) ts += ";5"; if ((c.format.flags & Inverse) == Inverse) ts += ";7"; return ts + "m"; } #endif // WINDOWS void PIScreen::SystemConsole::toUpperLeft() { #ifdef WINDOWS SetConsoleCursorPosition(PRIVATE->hOut, PRIVATE->ulcoord); #else printf("\e[H"); #endif } void PIScreen::SystemConsole::moveTo(int x, int y) { #ifdef WINDOWS PRIVATE->ccoord.X = x; PRIVATE->ccoord.Y = PRIVATE->ulcoord.Y + y; SetConsoleCursorPosition(PRIVATE->hOut, PRIVATE->ccoord); #else printf("\e[%d;%dH", y + 1, x + 1); #endif } void PIScreen::SystemConsole::clearScreen() { #ifdef WINDOWS toUpperLeft(); FillConsoleOutputAttribute(PRIVATE->hOut, PRIVATE->dattr, width * (height + 1), PRIVATE->ulcoord, &PRIVATE->written); FillConsoleOutputCharacter(PRIVATE->hOut, ' ', width * (height + 1), PRIVATE->ulcoord, &PRIVATE->written); #else printf("\e[0m\e[H\e[J"); #endif } void PIScreen::SystemConsole::clearScreenLower() { #ifdef WINDOWS 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); #else printf("\e[0m\e[J"); #endif } void PIScreen::SystemConsole::hideCursor() { #ifdef WINDOWS PRIVATE->curinfo.bVisible = false; SetConsoleCursorInfo(PRIVATE->hOut, &PRIVATE->curinfo); #else printf("\e[?25l"); #endif } void PIScreen::SystemConsole::showCursor() { #ifdef WINDOWS PRIVATE->curinfo.bVisible = true; SetConsoleCursorInfo(PRIVATE->hOut, &PRIVATE->curinfo); #else printf("\e[?25h"); #endif } PIScreen::PIScreen(bool startNow, PIKbdListener::KBFunc slot): PIThread(), drawer_(console.cells), root("rootTile") { setName("screen"); setPriority(piLow); needLockRun(true); mouse_ = false; ret_func = slot; tile_focus = tile_dialog = 0; root.screen = this; listener = new PIKbdListener(key_eventS, this, startNow); CONNECTU(listener, mouseEvent, this, mouse_event); CONNECTU(listener, wheelEvent, this, wheel_event); if (startNow) start(); } PIScreen::~PIScreen() { if (isRunning()) stop(); PIThread::waitForFinish(10); listener->waitForFinish(10); delete listener; } void PIScreen::setMouseEnabled(bool on) { mouse_ = on; //lock(); console.mouse_x = console.mouse_y = -1; //unlock(); } 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_); } PIVector PIScreen::prepareMouse(PIKbdListener::MouseEvent * e) { PIVector ret; if (!mouse_ || !e) return ret; console.mouse_x = e->x; console.mouse_y = e->y; PIVector tl = tilesUnderMouse(e->x, e->y); bool ff = false; piForeachR (PIScreenTile * t, tl) { if (!ff) { if (t->focus_flags[FocusOnMouse] && (e->action == PIKbdListener::MouseButtonPress)) { t->setFocus(); ff = true; } if (t->focus_flags[FocusOnWheel] && (e->action == PIKbdListener::MouseWheel)) { t->setFocus(); ff = true; } } } return tl; } PIVector PIScreen::tilesUnderMouse(int x, int y) { PIVector ret; if (x < 0 || x >= console.width || y < 0 || y >= console.height) return ret; PIScreenTile * ct = tile_dialog ? tile_dialog : rootTile(); bool f = true; while (ct) { if (!f) ret << ct; f = false; ct = ct->childUnderMouse(x, y); } return ret; } void PIScreen::mouse_event(PIKbdListener::MouseEvent me) { PIVector tl = prepareMouse(&me); if (tl.isEmpty()) return; piForeachR (PIScreenTile * t, tl) if (t->mouseEvent(me)) piBreak; } void PIScreen::wheel_event(PIKbdListener::WheelEvent we) { PIVector tl = prepareMouse(&we); if (tl.isEmpty()) return; piForeachR (PIScreenTile * t, tl) if (t->wheelEvent(we)) piBreak; } 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[NextByArrowsHorizontal]) { if (key.key == PIKbdListener::LeftArrow) next = -1; if (key.key == PIKbdListener::RightArrow) next = 1; } if (tile_focus->focus_flags[NextByArrowsVertical]) { if (key.key == PIKbdListener::UpArrow) next = -1; if (key.key == PIKbdListener::DownArrow) 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() { listener->start(); 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(); int dx = tile_dialog->x_ - 1, dy = tile_dialog->y_ - 1, dw = tile_dialog->width_, dh = tile_dialog->height_; drawer_.drawFrame(dx, dy, dx + dw + 1, dy + dh + 1, (Color)tile_dialog->back_format.color_char, (Color)tile_dialog->back_format.color_back, (CharFlags)tile_dialog->back_format.flags); tile_dialog->drawEventInternal(&drawer_); } console.print(); } void PIScreen::end() { listener->stop(); console.end(); } PIScreenTile * PIScreen::tileByName(const PIString & name) { PIVector tl(tiles()); piForeach (PIScreenTile * t, tl) if (t->name() == name) return t; return 0; }