From 6855c842465a2960e1ddbb6df20b1289a053a80c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B5=D0=BB=D0=B8=D0=BF=D0=B5=D0=BD=D0=BA=D0=BE=20?= =?UTF-8?q?=D0=98=D0=B2=D0=B0=D0=BD?= Date: Tue, 10 Mar 2015 10:39:58 +0000 Subject: [PATCH] tmp PIScreen git-svn-id: svn://db.shs.com.ru/pip@10 12ceb7fc-bf1f-11e4-8940-5bc7170c53b5 --- .kdev4/pip.kdev4 | 20 + src/code/picodemodule.h | 26 + src/console/piconsole.cpp | 1106 +++++++++++++++++++++++++++ src/console/piconsole.h | 486 ++++++++++++ src/console/piconsolemodule.h | 28 + src/console/pikbdlistener.cpp | 297 +++++++ src/console/pikbdlistener.h | 169 ++++ src/console/piscreen.cpp | 494 ++++++++++++ src/console/piscreen.h | 157 ++++ src/console/piscreendrawer.cpp | 165 ++++ src/console/piscreendrawer.h | 49 ++ src/console/piscreentile.cpp | 265 +++++++ src/console/piscreentile.h | 92 +++ src/console/piscreentiles.cpp | 277 +++++++ src/console/piscreentiles.h | 86 +++ src/console/piscreentypes.h | 134 ++++ src/containers/picontainersmodule.h | 25 + src/core/pibase.h | 444 +++++++++++ src/core/picoremodule.h | 30 + src/io/piiomodule.h | 38 + src/math/pimathmodule.h | 29 + src/system/pilibrary.cpp | 107 +++ src/system/pilibrary.h | 47 ++ src/system/pisystemmodule.h | 31 + src/thread/pithreadmodule.h | 27 + 25 files changed, 4629 insertions(+) create mode 100644 .kdev4/pip.kdev4 create mode 100644 src/code/picodemodule.h create mode 100644 src/console/piconsole.cpp create mode 100644 src/console/piconsole.h create mode 100644 src/console/piconsolemodule.h create mode 100644 src/console/pikbdlistener.cpp create mode 100644 src/console/pikbdlistener.h create mode 100644 src/console/piscreen.cpp create mode 100644 src/console/piscreen.h create mode 100644 src/console/piscreendrawer.cpp create mode 100644 src/console/piscreendrawer.h create mode 100644 src/console/piscreentile.cpp create mode 100644 src/console/piscreentile.h create mode 100644 src/console/piscreentiles.cpp create mode 100644 src/console/piscreentiles.h create mode 100644 src/console/piscreentypes.h create mode 100644 src/containers/picontainersmodule.h create mode 100644 src/core/pibase.h create mode 100644 src/core/picoremodule.h create mode 100644 src/io/piiomodule.h create mode 100644 src/math/pimathmodule.h create mode 100644 src/system/pilibrary.cpp create mode 100644 src/system/pilibrary.h create mode 100644 src/system/pisystemmodule.h create mode 100644 src/thread/pithreadmodule.h diff --git a/.kdev4/pip.kdev4 b/.kdev4/pip.kdev4 new file mode 100644 index 00000000..e70de97b --- /dev/null +++ b/.kdev4/pip.kdev4 @@ -0,0 +1,20 @@ +[CMake] +Build Directory Count=1 +Current Build Directory Index=0 +ProjectRootRelative=./ + +[CMake][CMake Build Directory 0] +Build Directory Path=file:///home/peri4/pprojects/pip_build +Build Type=Debug +CMake Binary=file:///usr/bin/cmake +Environment Profile= +Extra Arguments= +Install Directory= + +[Defines And Includes][Compiler] +Name=GCC +Path=gcc +Type=GCC + +[Project] +VersionControlSupport=kdevsubversion diff --git a/src/code/picodemodule.h b/src/code/picodemodule.h new file mode 100644 index 00000000..26d6ef0a --- /dev/null +++ b/src/code/picodemodule.h @@ -0,0 +1,26 @@ +/* + PIP - Platform Independent Primitives + Module includes + 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 . +*/ + +#ifndef PICODEMODULE_H +#define PICODEMODULE_H + +#include "picodeinfo.h" +#include "picodeparser.h" + +#endif // PICODEMODULE_H diff --git a/src/console/piconsole.cpp b/src/console/piconsole.cpp new file mode 100644 index 00000000..cbf11bd3 --- /dev/null +++ b/src/console/piconsole.cpp @@ -0,0 +1,1106 @@ +/* + 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 "piconsole.h" +#include "pipeer.h" +#include "piprotocol.h" +#include "pidiagnostics.h" +#include "pisystemmonitor.h" +#ifndef WINDOWS +# include +# include +# include +#else +# include +# define COMMON_LVB_UNDERSCORE 0x8000 +#endif + + +/** \class PIConsole + * \brief Console output class + * \details + * \section PIConsole_sec0 Synopsis + * This class provides output to console with automatic alignment and update. + * It supports tabs, keyboard listening, formats and colors. + * + * \section PIConsole_sec1 Layout + * %PIConsole 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 PIConsole_sec2 Keyboard usage + * %PIConsole should to be single in application. %PIConsole 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 + * + **/ + + +extern PIMutex __PICout_mutex__; + + +PRIVATE_DEFINITION_START(PIConsole) +#ifdef WINDOWS + void getWinCurCoord() {GetConsoleScreenBufferInfo(hOut, &csbi); ccoord = csbi.dwCursorPosition;} + COORD & getWinCoord(int dx = 0, int dy = 0) {getWinCurCoord(); ccoord.X += dx; ccoord.Y += dy; return ccoord;} + void * hOut; + CONSOLE_SCREEN_BUFFER_INFO sbi, csbi; + CONSOLE_CURSOR_INFO curinfo; + COORD ccoord, ulcoord; + WORD dattr; + DWORD smode, written; +#endif +PRIVATE_DEFINITION_END(PIConsole) + + +PIConsole::PIConsole(bool startNow, PIKbdListener::KBFunc slot): PIThread() { + setName("console"); + setPriority(piLow); + needLockRun(true); + ret_func = slot; + num_format = systime_format = 0; + vid = 0; + cur_tab = width = height = pwidth = pheight = max_y = 0; + def_align = Nothing; + tabs.reserve(16); +#ifdef WINDOWS + PRIVATE->ulcoord.X = 0; + PRIVATE->hOut = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(PRIVATE->hOut, &PRIVATE->sbi); + PRIVATE->dattr = PRIVATE->sbi.wAttributes; + width = PRIVATE->sbi.srWindow.Right - PRIVATE->sbi.srWindow.Left; + height = 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); + width = ws.ws_col; + height = ws.ws_row; +#endif + tabs.reserve(16); + addTab("main"); + listener = new PIKbdListener(key_event, this); + peer = 0; + server_mode = pause_ = false; + state = Disconnected; + peer_timer.addDelimiter(20); + peer_timer.setName("__S__PIConsole::peer_timer"); + CONNECT2(void, void * , int, &peer_timer, tickEvent, this, peerTimer); + if (startNow) start(); +} + + +PIConsole::~PIConsole() { + stopPeer(); + if (isRunning()) + stop(); + clearTabs(false); + delete listener; +#ifdef WINDOWS + SetConsoleMode(PRIVATE->hOut, PRIVATE->smode); + SetConsoleTextAttribute(PRIVATE->hOut, PRIVATE->dattr); +#endif +} + + +int PIConsole::addTab(const PIString & name, char bind_key) { + if (isRunning()) lock(); + tabs.push_back(Tab(name, bind_key)); + cur_tab = tabs.size() - 1; + if (isRunning()) unlock(); + return tabs.size(); +} + + +void PIConsole::removeTab(uint index) { + if (index >= tabs.size()) return; + if (isRunning()) lock(); + tabs.remove(index); + if (cur_tab >= tabs.size()) cur_tab = tabs.size() - 1; + if (isRunning()) unlock(); +} + + +void PIConsole::removeTab(const PIString & name) { + uint index = tabs.size() + 1; + for (uint i = 0; i < tabs.size(); ++i) { + if (tabs[i].name == name) { + index = i; + break; + } + } + removeTab(index); +} + + +void PIConsole::clearTab(uint index) { + if (index >= tabs.size()) return; + lock(); + tabs[index].columns.clear(); + if (cur_tab == index) { + clearScreen(); + fillLabels(); + } + if (cur_tab >= tabs.size()) cur_tab = tabs.size() - 1; + unlock(); +} + + +void PIConsole::clearTab(const PIString & name) { + uint index = tabs.size() + 1; + for (uint i = 0; i < tabs.size(); ++i) { + if (tabs[i].name == name) { + index = i; + break; + } + } + clearTab(index); +} + + +void PIConsole::update() { + lock(); + fillLabels(); + unlock(); +} + + +bool PIConsole::setTab(uint index) { + if (index >= tabs.size()) + return false; + if (!isRunning()) { + cur_tab = index; + return true; + } + lock(); + __PICout_mutex__.lock(); + cur_tab = index; + clearScreen(); + fillLabels(); + __PICout_mutex__.unlock(); + unlock(); + return true; +} + + +bool PIConsole::setTab(const PIString & name) { + uint index = tabs.size() + 1; + for (uint i = 0; i < tabs.size(); ++i) { + if (tabs[i].name == name) { + index = i; + break; + } + } + return setTab(index); +} + + +bool PIConsole::setTabBindKey(uint index, char bind_key) { + if (index >= tabs.size()) + return false; + tabs[index].key = bind_key; + return true; +} + + +bool PIConsole::setTabBindKey(const PIString & name, char bind_key) { + uint index =tabs.size() + 1; + for (uint i = 0; i < tabs.size(); ++i) { + if (tabs[i].name == name) { + index = i; + break; + } + } + return setTabBindKey(index, bind_key); +} + + +void PIConsole::key_event(PIKbdListener::KeyEvent key, void * t) { + PIConsole * p = (PIConsole * )t; + int ct = p->cur_tab; + if (key.key == PIKbdListener::LeftArrow) { + do { + ct--; + if (ct < 0) return; + } while (p->tabs[ct].key == 0); + p->setTab(ct); + return; + } + if (key.key == PIKbdListener::RightArrow) { + do { + ct++; + if (ct >= p->tabs.size_s()) return; + } while (p->tabs[ct].key == 0); + p->setTab(ct); + return; + } + for (uint i = 0; i < p->tabsCount(); ++i) { + if (p->tabs[i].key == key.key) { + p->setTab(i); + return; + } + } + if (p->ret_func != 0) p->ret_func(key, t); + p->keyPressed(key, t); +} + + +void PIConsole::clearVariables(bool clearScreen) { + if (isRunning()) lock(); + if (clearScreen && isRunning()) { + toUpperLeft(); + clearScreenLower(); + } + columns().clear(); + if (isRunning()) unlock(); +} + + +void PIConsole::stop(bool clear) { + PIThread::stop(true); + if (clear) clearScreen(); + moveTo(0, max_y + 4); + showCursor(); + couts(fstr(Normal)); +#ifdef WINDOWS + SetConsoleMode(PRIVATE->hOut, PRIVATE->smode); + SetConsoleTextAttribute(PRIVATE->hOut, PRIVATE->dattr); +#endif + fflush(0); +} + + +PIString PIConsole::fstr(FormatFlags f) { + num_format = systime_format = 0; + if (f[PIConsole::Dec]) num_format = 0; + if (f[PIConsole::Hex]) num_format = 1; + if (f[PIConsole::Oct]) num_format = 2; + if (f[PIConsole::Bin]) num_format = 4; + if (f[PIConsole::Scientific]) num_format = 3; + if (f[PIConsole::SystemTimeSplit]) systime_format = 0; + if (f[PIConsole::SystemTimeSeconds]) systime_format = 1; + +#ifdef WINDOWS + WORD attr = 0; + + if (f[PIConsole::Inverse]) { + if (f[PIConsole::Red]) attr |= BACKGROUND_RED; + if (f[PIConsole::Green]) attr |= BACKGROUND_GREEN; + if (f[PIConsole::Blue]) attr |= BACKGROUND_BLUE; + if (f[PIConsole::Yellow]) attr |= (BACKGROUND_RED | BACKGROUND_GREEN); + if (f[PIConsole::Magenta]) attr |= (BACKGROUND_RED | BACKGROUND_BLUE); + if (f[PIConsole::Cyan]) attr |= (BACKGROUND_GREEN | BACKGROUND_BLUE); + if (f[PIConsole::White]) attr |= (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE); + if (f[PIConsole::BackRed]) attr |= FOREGROUND_RED; + if (f[PIConsole::BackGreen]) attr |= FOREGROUND_GREEN; + if (f[PIConsole::BackBlue]) attr |= FOREGROUND_BLUE; + if (f[PIConsole::BackYellow]) attr |= (FOREGROUND_RED | FOREGROUND_GREEN); + if (f[PIConsole::BackMagenta]) attr |= (FOREGROUND_RED | FOREGROUND_BLUE); + if (f[PIConsole::BackCyan]) attr |= (FOREGROUND_GREEN | FOREGROUND_BLUE); + if (f[PIConsole::BackWhite]) attr |= (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + if ((attr & BACKGROUND_RED) + (attr & BACKGROUND_GREEN) + (attr & BACKGROUND_BLUE) == 0) + attr |= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + } else { + if (f[PIConsole::Red]) attr |= FOREGROUND_RED; + if (f[PIConsole::Green]) attr |= FOREGROUND_GREEN; + if (f[PIConsole::Blue]) attr |= FOREGROUND_BLUE; + if (f[PIConsole::Yellow]) attr |= (FOREGROUND_RED | FOREGROUND_GREEN); + if (f[PIConsole::Magenta]) attr |= (FOREGROUND_RED | FOREGROUND_BLUE); + if (f[PIConsole::Cyan]) attr |= (FOREGROUND_GREEN | FOREGROUND_BLUE); + if (f[PIConsole::White]) attr |= (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + if (f[PIConsole::BackRed]) attr |= BACKGROUND_RED; + if (f[PIConsole::BackGreen]) attr |= BACKGROUND_GREEN; + if (f[PIConsole::BackBlue]) attr |= BACKGROUND_BLUE; + if (f[PIConsole::BackYellow]) attr |= (BACKGROUND_RED | BACKGROUND_GREEN); + if (f[PIConsole::BackMagenta]) attr |= (BACKGROUND_RED | BACKGROUND_BLUE); + if (f[PIConsole::BackCyan]) attr |= (BACKGROUND_GREEN | BACKGROUND_BLUE); + if (f[PIConsole::BackWhite]) attr |= (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE); + if ((attr & FOREGROUND_RED) + (attr & FOREGROUND_GREEN) + (attr & FOREGROUND_BLUE) == 0) + attr |= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + } + if (f[PIConsole::Bold]) attr |= FOREGROUND_INTENSITY; + if (f[PIConsole::Underline]) attr |= COMMON_LVB_UNDERSCORE; + + SetConsoleTextAttribute(PRIVATE->hOut, attr); + return PIString(); +#else + PIString ts("\e[0"); + + if (f[PIConsole::Bold]) ts += ";1"; + if (f[PIConsole::Faint]) ts += ";2"; + if (f[PIConsole::Italic]) ts += ";3"; + if (f[PIConsole::Underline]) ts += ";4"; + if (f[PIConsole::Blink]) ts += ";5"; + if (f[PIConsole::Inverse]) ts += ";7"; + + if (f[PIConsole::Black]) ts += ";30"; + if (f[PIConsole::Red]) ts += ";31"; + if (f[PIConsole::Green]) ts += ";32"; + if (f[PIConsole::Yellow]) ts += ";33"; + if (f[PIConsole::Blue]) ts += ";34"; + if (f[PIConsole::Magenta]) ts += ";35"; + if (f[PIConsole::Cyan]) ts += ";36"; + if (f[PIConsole::White]) ts += ";37"; + + if (f[PIConsole::BackBlack]) ts += ";40"; + if (f[PIConsole::BackRed]) ts += ";41"; + if (f[PIConsole::BackGreen]) ts += ";42"; + if (f[PIConsole::BackYellow]) ts += ";43"; + if (f[PIConsole::BackBlue]) ts += ";44"; + if (f[PIConsole::BackMagenta]) ts += ";45"; + if (f[PIConsole::BackCyan]) ts += ";46"; + if (f[PIConsole::BackWhite]) ts += ";47"; + + return ts + "m"; +#endif +} + + +inline int PIConsole::couts(const bool v) {return (v ? printf("true") : printf("false"));} +inline int PIConsole::couts(const char v) {return printf("%c", v);} +inline int PIConsole::couts(const short v) { + switch (num_format) {case (1): return printf("0x%.4hX", v); break; case (2): return printf("%o", v); break; case (4): return printf("%s", toBin(&v, 2)); break; default: return printf("%hd", v); break;} +} +inline int PIConsole::couts(const int v) { + switch (num_format) {case (1): return printf("0x%.8X", v); break; case (2): return printf("%o", v); break; case (4): return printf("%s", toBin(&v, 4)); break; default: return printf("%d", v); break;} +} +inline int PIConsole::couts(const long v) { + switch (num_format) {case (1): return printf("0x%.16lX", v); break; case (2): return printf("%lo", v); break; case (4): return printf("%s", toBin(&v, sizeof(v))); break; default: return printf("%ld", v); break;} +} +inline int PIConsole::couts(const llong v) { + switch (num_format) {case (1): return printf("0x%.16llX", v); break; case (2): return printf("%llo", v); break; case (4): return printf("%s", toBin(&v, sizeof(v))); break; default: return printf("%lld", v); break;} +} +inline int PIConsole::couts(const uchar v) { + switch (num_format) {case (1): return printf("0x%.2X", v); break; case (2): return printf("%o", v); break; case (4): return printf("%s", toBin(&v, 1)); break; default: return printf("%u", v); break;} +} +inline int PIConsole::couts(const ushort v) { + switch (num_format) {case (1): return printf("0x%.4hX", v); break; case (2): return printf("%o", v); break; case (4): return printf("%s", toBin(&v, 2)); break; default: return printf("%hu", v); break;} +} +inline int PIConsole::couts(const uint v) { + switch (num_format) {case (1): return printf("0x%.8X", v); break; case (2): return printf("%o", v); break; case (4): return printf("%s", toBin(&v, 4)); break; default: return printf("%u", v); break;} +} +inline int PIConsole::couts(const ulong v) { + switch (num_format) {case (1): return printf("0x%.16lX", v); break; case (2): return printf("%lo", v); break; case (4): return printf("%s", toBin(&v, sizeof(v))); break; default: return printf("%lu", v); break;} +} +inline int PIConsole::couts(const ullong v) { + switch (num_format) {case (1): return printf("0x%.16llX", v); break; case (2): return printf("%llo", v); break; case (4): return printf("%s", toBin(&v, sizeof(v))); break; default: return printf("%llu", v); break;} +} +inline int PIConsole::couts(const float v) { + switch (num_format) {case (3): return printf("%e", v); break; default: return printf("%.5g", v); break;} +} +inline int PIConsole::couts(const double v) { + switch (num_format) {case (3): return printf("%le", v); break; default: return printf("%.5lg", v); break;} +} +inline int PIConsole::couts(const PISystemTime & v) { + switch (systime_format) {case (1): return printf("%.6lg", v.toSeconds()); break; + default: return couts(v.seconds) + printf(" s, ") + couts(v.nanoseconds) + printf(" ns"); break;} +} + + +#ifdef WINDOWS +void PIConsole::toUpperLeft() {SetConsoleCursorPosition(PRIVATE->hOut, PRIVATE->ulcoord);} +void PIConsole::moveRight(int n) {SetConsoleCursorPosition(PRIVATE->hOut, PRIVATE->getWinCoord(n));} +void PIConsole::moveLeft(int n) {SetConsoleCursorPosition(PRIVATE->hOut, PRIVATE->getWinCoord(-n));} +void PIConsole::moveTo(int x, int y) {PRIVATE->ccoord.X = x; PRIVATE->ccoord.Y = PRIVATE->ulcoord.Y + y; SetConsoleCursorPosition(PRIVATE->hOut, PRIVATE->ccoord);} +void PIConsole::clearScreen() {couts(fstr(Normal)); toUpperLeft(); FillConsoleOutputAttribute(PRIVATE->hOut, PRIVATE->dattr, width * (height + 1), PRIVATE->ulcoord, &PRIVATE->written); + FillConsoleOutputCharacter(PRIVATE->hOut, ' ', width * (height + 1), PRIVATE->ulcoord, &PRIVATE->written);} +void PIConsole::clearScreenLower() {couts(fstr(Normal)); PRIVATE->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 PIConsole::clearLine() {PRIVATE->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 PIConsole::newLine() {PRIVATE->getWinCurCoord(); PRIVATE->ccoord.X = 0; PRIVATE->ccoord.Y++; SetConsoleCursorPosition(PRIVATE->hOut, PRIVATE->ccoord);} +void PIConsole::hideCursor() {PRIVATE->curinfo.bVisible = false; SetConsoleCursorInfo(PRIVATE->hOut, &PRIVATE->curinfo);} +void PIConsole::showCursor() {PRIVATE->curinfo.bVisible = true; SetConsoleCursorInfo(PRIVATE->hOut, &PRIVATE->curinfo);} +#endif + + +void PIConsole::begin() { +#ifdef WINDOWS + SetConsoleMode(PRIVATE->hOut, ENABLE_WRAP_AT_EOL_OUTPUT); +#endif + max_y = 0; + __PICout_mutex__.lock(); + clearScreen(); + hideCursor(); + fillLabels(); + __PICout_mutex__.unlock(); +} + + +void PIConsole::run() { + if (pause_) return; + uint cx, clen = 0; + int j; +#ifdef WINDOWS + GetConsoleScreenBufferInfo(PRIVATE->hOut, &PRIVATE->sbi); + width = PRIVATE->sbi.srWindow.Right - PRIVATE->sbi.srWindow.Left; + height = PRIVATE->sbi.srWindow.Bottom - PRIVATE->sbi.srWindow.Top; +#else + winsize ws; + ioctl(0, TIOCGWINSZ, &ws); + width = ws.ws_col; + height = ws.ws_row; +#endif + //fflush(0); return; + __PICout_mutex__.lock(); + if (pwidth != width || pheight != height) { + clearScreen(); + fillLabels(); + } + pwidth = width; + pheight = height; + col_cnt = columns().size(); + col_wid = (col_cnt > 0) ? width / col_cnt : width; + for (uint i = 0; i < col_cnt; ++i) { + PIVector & cvars(tabs[cur_tab].columns[i].variables); + cx = col_wid * i; + toUpperLeft(); + if (max_y < cvars.size()) max_y = cvars.size(); + j = 0; + piForeachC (Variable & tv, cvars) { + if (j > height - 3) continue; + j++; + moveRight(cx); + if (tv.type == 15) { + newLine(); + continue; + } + moveRight(tv.offset); + const void * ptr = 0; + if (tv.remote) { + if (tv.type == 0) { + rstr.clear(); + rba = tv.rdata; + rba >> rstr; + rstr.trim(); + ptr = &rstr; + } else + ptr = tv.rdata.data(); + } else + ptr = tv.ptr; + switch (tv.type) { + case 0: clen = printValue(ptr != 0 ? *(const PIString*)ptr : PIString(), tv.format); break; + case 1: clen = printValue(ptr != 0 ? *(const bool*)ptr : false, tv.format); break; + case 2: clen = printValue(ptr != 0 ? *(const int*)ptr : 0, tv.format); break; + case 3: clen = printValue(ptr != 0 ? *(const long*)ptr : 0l, tv.format); break; + case 4: clen = printValue(ptr != 0 ? *(const char*)ptr : char(0), tv.format); break; + case 5: clen = printValue(ptr != 0 ? *(const float*)ptr : 0.f, tv.format); break; + case 6: clen = printValue(ptr != 0 ? *(const double*)ptr : 0., tv.format); break; + case 7: clen = printValue(ptr != 0 ? *(const short*)ptr : short(0), tv.format); break; + case 8: clen = printValue(ptr != 0 ? *(const uint*)ptr : 0u, tv.format); break; + case 9: clen = printValue(ptr != 0 ? *(const ulong*)ptr : 0ul, tv.format); break; + case 10: clen = printValue(ptr != 0 ? *(const ushort*)ptr : ushort(0), tv.format); break; + case 11: clen = printValue(ptr != 0 ? *(const uchar*)ptr : uchar(0), tv.format); break; + case 12: clen = printValue(ptr != 0 ? *(const llong*)ptr : 0l, tv.format); break; + case 13: clen = printValue(ptr != 0 ? *(const ullong*)ptr: 0ull, tv.format); break; + case 20: clen = printValue(ptr != 0 ? *(const PISystemTime*)ptr: PISystemTime(), tv.format); break; + case 14: clen = printValue(bitsValue(ptr, tv.bitFrom, tv.bitCount), tv.format); break; + } + if (clen + tv.offset < (uint)col_wid) { + PIString ts = PIString( +#if defined(QNX) || defined(FREE_BSD) + col_wid - clen - tv.offset - 1, ' '); +#else + col_wid - clen - tv.offset, ' '); +#endif + printf("%s", ts.data()); + } + newLine(); + } + } +#ifdef WINDOWS + moveTo(0, max_y + 1); +#else + moveTo(0, max_y + 2); +#endif + fflush(0); + __PICout_mutex__.unlock(); +} + + +void PIConsole::fillLabels() { + if (!isRunning()) return; + uint cx, cy, mx = 0, dx; +#ifdef WINDOWS + GetConsoleScreenBufferInfo(PRIVATE->hOut, &PRIVATE->sbi); + width = PRIVATE->sbi.srWindow.Right - PRIVATE->sbi.srWindow.Left; + height = PRIVATE->sbi.srWindow.Bottom - PRIVATE->sbi.srWindow.Top; +#else + winsize ws; + ioctl(0, TIOCGWINSZ, &ws); + width = ws.ws_col; + height = ws.ws_row; +#endif + max_y = 0; + col_cnt = columns().size(); + col_wid = (col_cnt > 0) ? width / col_cnt : width; + for (uint i = 0; i < col_cnt; ++i) { + Column & ccol(tabs[cur_tab].columns[i]); + PIVector & cvars(ccol.variables); + if (ccol.alignment != Nothing) { + mx = 0; + piForeachC (Variable & j, cvars) + if (!j.isEmpty()) + if (mx < j.name.size()) + mx = j.name.size(); + mx += 2; + } + cx = col_wid * i; + cy = 1; + toUpperLeft(); + for (uint j = 0; j < cvars.size(); ++j) { + if (int(j) > height - 3) continue; + if (max_y < j) max_y = j; + moveRight(cx); + Variable & tv(cvars[j]); + cvars[j].nx = cx; + cvars[j].ny = cy; + if (tv.name.isEmpty()) { + cvars[j].offset = 0; + clearLine(); + newLine(); + cy++; + continue; + } + clearLine(); + //piCout << tv.name << tv.type << tv.ptr; + if (tv.type == 15) { + cvars[j].offset = cvars[j].name.length(); + cvars[j].nx += cvars[j].offset; + printLine(tv.name, cx, tv.format); + newLine(); + cy++; + continue; + } + if (!tv.isEmpty()) { + switch (ccol.alignment) { + case Nothing: + cvars[j].offset = (tv.name + ": ").length(); + cvars[j].nx += cvars[j].offset; + printValue(tv.name + ": ", tv.format); + break; + case Left: + cvars[j].offset = mx; + cvars[j].nx += cvars[j].offset; + printValue(tv.name + ": ", tv.format); + break; + case Right: + cvars[j].offset = mx; + cvars[j].nx += cvars[j].offset; + dx = mx - (tv.name + ": ").length(); + moveRight(dx); + printValue(tv.name + ": ", tv.format); + moveLeft(dx); + break; + } + } + newLine(); + cy++; + } + } +#ifdef WINDOWS + moveTo(0, max_y + 1); +#else + moveTo(0, max_y + 2); +#endif + if (!tabs[cur_tab].status.isEmpty()) { + printValue(tabs[cur_tab].status); + newLine(); + } + status(); + //couts(fstr(Normal)); + //fflush(0); +} + + +void PIConsole::status() { + Tab * ctab; + //clearLine(); + for (uint i = 0; i < tabsCount(); ++i) { + ctab = &tabs[i]; + if (ctab->key == 0) continue; + printValue(ctab->key, PIConsole::White | PIConsole::Bold); + if (i == cur_tab) + printValue(ctab->name + " ", PIConsole::BackYellow | PIConsole::Black); + else + printValue(ctab->name + " ", PIConsole::Cyan | PIConsole::Inverse); + printValue(" "); + } + newLine(); +} + + +int PIConsole::bitsValue(const void * src, int offset, int count) const { + int ret = 0, stbyte = offset / 8, cbit = offset - stbyte * 8; + char cbyte = reinterpret_cast(src)[stbyte]; + for (int i = 0; i < count; i++) { + ret |= ((cbyte >> cbit & 1) << i); + cbit++; + if (cbit == 8) { + cbit = 0; + stbyte++; + cbyte = reinterpret_cast(src)[stbyte]; + } + } + return ret; +} + + +const char * PIConsole::toBin(const void * d, int s) { + binstr.clear(); + uchar cc, b; + for (int i = 0; i < s; ++i) { + cc = ((const uchar *)d)[i]; + b = 1; + for (int j = 0; j < 8; ++j) { + binstr << (cc & b ? "1" : "0"); + b <<= 1; + } + if (i < s - 1) binstr << " "; + } + binstr.reverse(); + return binstr.data(); +} + + +#define ADD_VAR_BODY vid++; tv.id = vid; tv.name = name; tv.bitFrom = tv.bitCount = 0; tv.format = format; tv.remote = false; checkColumn(col); + +void PIConsole::addString(const PIString & name, int col, FormatFlags format) { + ADD_VAR_BODY tv.type = 15; tv.size = 0; tv.ptr = 0; column(col).push_back(tv);} +void PIConsole::addVariable(const PIString & name, const PIString * ptr, int col, FormatFlags format) { + ADD_VAR_BODY tv.type = 0; tv.size = 0; tv.ptr = ptr; column(col).push_back(tv);} +void PIConsole::addVariable(const PIString & name, const bool * ptr, int col, FormatFlags format) { + ADD_VAR_BODY tv.type = 1; tv.size = sizeof(*ptr); tv.ptr = ptr; column(col).push_back(tv);} +void PIConsole::addVariable(const PIString & name, const int * ptr, int col, FormatFlags format) { + ADD_VAR_BODY tv.type = 2; tv.size = sizeof(*ptr); tv.ptr = ptr; column(col).push_back(tv);} +void PIConsole::addVariable(const PIString & name, const long * ptr, int col, FormatFlags format) { + ADD_VAR_BODY tv.type = 3; tv.size = sizeof(*ptr); tv.ptr = ptr; column(col).push_back(tv);} +void PIConsole::addVariable(const PIString & name, const char * ptr, int col, FormatFlags format) { + ADD_VAR_BODY tv.type = 4; tv.size = sizeof(*ptr); tv.ptr = ptr; column(col).push_back(tv);} +void PIConsole::addVariable(const PIString & name, const float * ptr, int col, FormatFlags format) { + ADD_VAR_BODY tv.type = 5; tv.size = sizeof(*ptr); tv.ptr = ptr; column(col).push_back(tv);} +void PIConsole::addVariable(const PIString & name, const double * ptr, int col, FormatFlags format) { + ADD_VAR_BODY tv.type = 6; tv.size = sizeof(*ptr); tv.ptr = ptr; column(col).push_back(tv);} +void PIConsole::addVariable(const PIString & name, const short * ptr, int col, FormatFlags format) { + ADD_VAR_BODY tv.type = 7; tv.size = sizeof(*ptr); tv.ptr = ptr; column(col).push_back(tv);} +void PIConsole::addVariable(const PIString & name, const uint * ptr, int col, FormatFlags format) { + ADD_VAR_BODY tv.type = 8; tv.size = sizeof(*ptr); tv.ptr = ptr; column(col).push_back(tv);} +void PIConsole::addVariable(const PIString & name, const ulong * ptr, int col, FormatFlags format) { + ADD_VAR_BODY tv.type = 9; tv.size = sizeof(*ptr); tv.ptr = ptr; column(col).push_back(tv);} +void PIConsole::addVariable(const PIString & name, const ushort * ptr, int col, FormatFlags format) { + ADD_VAR_BODY tv.type = 10; tv.size = sizeof(*ptr); tv.ptr = ptr; column(col).push_back(tv);} +void PIConsole::addVariable(const PIString & name, const uchar * ptr, int col, FormatFlags format) { + ADD_VAR_BODY tv.type = 11; tv.size = sizeof(*ptr); tv.ptr = ptr; column(col).push_back(tv);} +void PIConsole::addVariable(const PIString & name, const llong * ptr, int col, FormatFlags format) { + ADD_VAR_BODY tv.type = 12; tv.size = sizeof(*ptr); tv.ptr = ptr; column(col).push_back(tv);} +void PIConsole::addVariable(const PIString & name, const ullong * ptr, int col, FormatFlags format) { + ADD_VAR_BODY tv.type = 13; tv.size = sizeof(*ptr); tv.ptr = ptr; column(col).push_back(tv);} +void PIConsole::addVariable(const PIString & name, const PISystemTime * ptr, int col, FormatFlags format) { + ADD_VAR_BODY tv.type = 20; tv.size = sizeof(*ptr); tv.ptr = ptr; column(col).push_back(tv);} +/** \brief Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + * \details This function add to column "column" next lines: + * * "protocol " + * * "Rec - receiverDeviceName": \a PIProtocol::receiverDeviceState + * * "Send - senderDeviceName": \a PIProtocol::senderDeviceState + * * "Received count": \a PIProtocol::receiveCount + * * "Invalid count": \a PIProtocol::wrongCount + * * "Missed count": \a PIProtocol::missedCount + * * "Sended count": \a PIProtocol::sendCount + * * "Immediate Frequency, Hz": \a PIProtocol::immediateFrequency + * * "Integral Frequency, Hz": \a PIProtocol::integralFrequency + * * "Receive speed": \a PIProtocol::receiveSpeed + * * "Send speed": \a PIProtocol::sendSpeed + * * "Receiver history size": \a PIProtocol::receiverHistorySize + * * "Sender history size": \a PIProtocol::senderHistorySize + * * "Disconnect Timeout, s": \a PIProtocol::disconnectTimeout + * * "Quality": \a PIProtocol::quality + * */ +void PIConsole::addVariable(const PIString & name, const PIProtocol * ptr, int col, FormatFlags format) { + addString("protocol " + name, col, format | PIConsole::Bold); + addVariable("Rec - " + ptr->receiverDeviceName(), ptr->receiverDeviceState_ptr(), col, format); + addVariable("Send - " + ptr->senderDeviceName(), ptr->senderDeviceState_ptr(), col, format); + addVariable("Received count", ptr->receiveCount_ptr(), col, format); + addVariable("Invalid count", ptr->wrongCount_ptr(), col, format); + addVariable("Missed count", ptr->missedCount_ptr(), col, format); + addVariable("Sended count", ptr->sendCount_ptr(), col, format); + addVariable("Immediate Frequency, Hz", ptr->immediateFrequency_ptr(), col, format); + addVariable("Integral Frequency, Hz", ptr->integralFrequency_ptr(), col, format); + addVariable("Receive speed", ptr->receiveSpeed_ptr(), col, format); + addVariable("Send speed", ptr->sendSpeed_ptr(), col, format); + addVariable("Receiver history size", ptr->receiverHistorySize_ptr(), col, format); + addVariable("Sender history size", ptr->senderHistorySize_ptr(), col, format); + addVariable("Disconnect Timeout, s", ptr->disconnectTimeout_ptr(), col, format); + addVariable("Quality", ptr->quality_ptr(), col, format); +} +/** \brief Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + * \details This function add to column "column" next lines: + * * " diagnostics" + * * "Received count": \a PIDiagnostics::receiveCount + * * "Invalid count": \a PIDiagnostics::wrongCount + * * "Sended count": \a PIDiagnostics::sendCount + * * "Immediate Frequency, Hz": \a PIDiagnostics::immediateFrequency + * * "Integral Frequency, Hz": \a PIDiagnostics::integralFrequency + * * "Receive speed": \a PIDiagnostics::receiveSpeed + * * "Send speed": \a PIDiagnostics::sendSpeed + * * "Quality": \a PIDiagnostics::quality + * */ +void PIConsole::addVariable(const PIString & name, const PIDiagnostics * ptr, int col, FormatFlags format) { + addString(name + " diagnostics", col, format | PIConsole::Bold); + addVariable("Received count", ptr->receiveCount_ptr(), col, format); + addVariable("Invalid count", ptr->wrongCount_ptr(), col, format); + addVariable("Sended count", ptr->sendCount_ptr(), col, format); + addVariable("Immediate Frequency, Hz", ptr->immediateFrequency_ptr(), col, format); + addVariable("Integral Frequency, Hz", ptr->integralFrequency_ptr(), col, format); + addVariable("Receive speed", ptr->receiveSpeed_ptr(), col, format); + addVariable("Send speed", ptr->sendSpeed_ptr(), col, format); + addVariable("Quality", ptr->quality_ptr(), col, format); +} +void PIConsole::addVariable(const PIString & name, const PISystemMonitor * ptr, int col, FormatFlags format) { + addString("monitor " + name, col, format | PIConsole::Bold); + addVariable("PID", &(ptr->statistic().ID), col, format); + //addVariable("state", &(ptr->statistic().state), col, format); + addVariable("threads", &(ptr->statistic().threads), col, format); + addVariable("priority", &(ptr->statistic().priority), col, format); + addVariable("memory physical", &(ptr->statistic().physical_memsize_readable), col, format); + addVariable("memory shared", &(ptr->statistic().share_memsize_readable), col, format); + addVariable("cpu load kernel", &(ptr->statistic().cpu_load_system), col, format); + addVariable("cpu load user", &(ptr->statistic().cpu_load_user), col, format); +} +void PIConsole::addBitVariable(const PIString & name, const void * ptr, int fromBit, int bitCount, int col, FormatFlags format) { + vid++; tv.id = vid; tv.size = sizeof(ullong); tv.name = name; tv.bitFrom = fromBit; tv.bitCount = bitCount; tv.type = 14; tv.ptr = ptr; tv.format = format; + checkColumn(col); column(col).push_back(tv);} +void PIConsole::addEmptyLine(int col, uint count) { + tv.id = 0; tv.size = 0; tv.name = ""; tv.type = 0; tv.ptr = 0; tv.format = Normal; + for (uint i = 0; i < count; ++i) { + checkColumn(col); + column(col).push_back(tv); + } +} + + +PIString PIConsole::getString(int x, int y) { + bool run = isRunning(); + if (run) PIThread::stop(true); + listener->setActive(false); + msleep(50); +#ifdef WINDOWS + moveTo(x - 1, y - 1); +#else + moveTo(x, y); +#endif + showCursor(); + PIByteArray ba(4096); +#ifdef CC_VC + int ret = scanf_s(" %s", ba.data()); +#else + int ret = scanf(" %s", ba.data()); +#endif + listener->setActive(true); + if (run) start(); + if (ret >= 1) return PIString(ba); + else return PIString(); +} + + +PIString PIConsole::getString(const PIString & name) { + piForeachC (Column & i, tabs[cur_tab].columns) + piForeachC (Variable & j, i.variables) + if (j.name == name) + return getString(j.nx + 1, j.ny); + return PIString(); +} + + +#define PRINT_VAR_BODY couts(fstr(format)); int ret = couts(value); couts(fstr(PIConsole::Dec)); return ret; + +inline void PIConsole::printLine(const PIString & value, int dx, FormatFlags format) { + int i = width - value.length() - dx; +#if defined(QNX) || defined(FREE_BSD) + --i; +#endif + PIString ts = fstr(format); + couts(ts); + if (i >= 0) ts = value + PIString(i, ' '); + else ts = value.left(value.size() + i); + couts(ts); + couts(fstr(Dec)); +} +inline int PIConsole::printValue(const PIString & value, FormatFlags format) { + couts(fstr(format)); + int ret = couts(value); + fstr(PIConsole::Dec); + return ret; +} +inline int PIConsole::printValue(const char * value, FormatFlags format) {PRINT_VAR_BODY} +inline int PIConsole::printValue(const bool value, FormatFlags format) {PRINT_VAR_BODY} +inline int PIConsole::printValue(const int value, FormatFlags format) {PRINT_VAR_BODY} +inline int PIConsole::printValue(const long value, FormatFlags format) {PRINT_VAR_BODY} +inline int PIConsole::printValue(const llong value, FormatFlags format) {PRINT_VAR_BODY} +inline int PIConsole::printValue(const float value, FormatFlags format) {PRINT_VAR_BODY} +inline int PIConsole::printValue(const double value, FormatFlags format) {PRINT_VAR_BODY} +inline int PIConsole::printValue(const char value, FormatFlags format) {PRINT_VAR_BODY} +inline int PIConsole::printValue(const short value, FormatFlags format) {PRINT_VAR_BODY} +inline int PIConsole::printValue(const uchar value, FormatFlags format) {PRINT_VAR_BODY} +inline int PIConsole::printValue(const ushort value, FormatFlags format) {PRINT_VAR_BODY} +inline int PIConsole::printValue(const uint value, FormatFlags format) {PRINT_VAR_BODY} +inline int PIConsole::printValue(const ulong value, FormatFlags format) {PRINT_VAR_BODY} +inline int PIConsole::printValue(const ullong value, FormatFlags format) {PRINT_VAR_BODY} +inline int PIConsole::printValue(const PISystemTime & value, FormatFlags format) {PRINT_VAR_BODY} + + + +void PIConsole::startServer(const PIString & name) { + stopPeer(); + server_mode = true; + peer = new PIPeer("_rcs_:" + name); + CONNECT2(void, const PIString & , const PIByteArray &, peer, dataReceivedEvent, this, peerReceived); + CONNECT1(void, const PIString & , peer, peerDisconnectedEvent, this, peerDisconnectedEvent); + peer_timer.start(50.); + serverSendInfo(); +} + + +void PIConsole::stopPeer() { + remote_clients.clear(); + peer_timer.stop(); + if (peer != 0) delete peer; + peer = 0; + state = Disconnected; +} + + +PIStringList PIConsole::clients() const { + PIStringList sl; + if (peer == 0) return sl; + piForeachC (PIPeer::PeerInfo & i, peer->allPeers()) { + if (i.name.left(6) != "_rcc_:") continue; + sl << i.name.right(i.name.length() - 6); + } + return sl; +} + + +void PIConsole::listenServers() { + stopPeer(); + server_mode = false; + server_name.clear(); + srand(PISystemTime::current().nanoseconds); + peer = new PIPeer("_rcc_:" + PIDateTime::current().toString("hhmmssddMMyy_") + PIString::fromNumber(rand())); + CONNECT2(void, const PIString & , const PIByteArray &, peer, dataReceivedEvent, this, peerReceived); + peer_timer.start(100.); +} + + +PIStringList PIConsole::availableServers() const { + PIStringList sl; + if (peer == 0) return sl; + piForeachC (PIPeer::PeerInfo & i, peer->allPeers()) { + if (i.name.left(6) != "_rcs_:") continue; + sl << i.name.right(i.name.length() - 6); + } + return sl; +} + + +void PIConsole::connectToServer(const PIString & name) { + if (peer == 0) listenServers(); + server_name = name; +} + + +void PIConsole::disconnect() { + stopPeer(); +} + + +void PIConsole::serverSendInfo() { + if (peer == 0) return; + PIByteArray ba; + ba << int(0xAA); + peer->sendToAll(ba); +} + + +void PIConsole::serverSendData() { + if (peer == 0) return; + PIByteArray ba; + PIVector content; + piForeach (Tab & t, tabs) + piForeach (Column & c, t.columns) + piForeach (Variable & v, c.variables) + if (!v.isEmpty() && v.id > 0) { + VariableContent vc; + vc.id = v.id; + v.writeData(vc.rdata); + content << vc; + } + piForeach (RemoteClient & rc, remote_clients) { + ba.clear(); + switch (rc.state) { + case FetchingData: + ba << int(0xCC) << tabs; + //piCout << "server send const data" << rc.name << ba.size_s(); + break; + case Committing: + ba << int(0xDD); + break; + case Connected: + ba << int(0xEE) << content; + //piCout << "send data" << ba.size(); + break; + default: break; + } + if (!ba.isEmpty()) + peer->send(rc.name, ba); + } +} + + +PIConsole::RemoteClient & PIConsole::remoteClient(const PIString & fname) { + piForeach (RemoteClient & i, remote_clients) + if (i.name == fname) + return i; + remote_clients << RemoteClient(fname); + return remote_clients.back(); +} + + +void PIConsole::peerReceived(const PIString & from, const PIByteArray & data) { + int type; + PIByteArray ba(data); + ba >> type; + //piCout << "rec packet from" << from << "type" << PICoutManipulators::Hex << type; + if (server_mode) { + if (from.left(5) != "_rcc_") return; + //PIString rcn = from.right(from.length() - 6); + RemoteClient & rc(remoteClient(from)); + switch (type) { + case 0xBB: // fetch const data request + //piCout << "fetch data request from" << from << rc.state; + if (rc.state != Connected) + rc.state = FetchingData; + break; + case 0xCC: // const data commit + //piCout << "commit from" << from; + if (rc.state != Connected) + rc.state = Connected; + break; + default: break; + } + } else { + PIVector content; + PIMap vids; + if (from.left(5) != "_rcs_") return; + //PIString rcn = from.right(from.length() - 6); + switch (type) { + case 0xAA: // new server + //piCout << "new server" << rcn; + break; + case 0xCC: // const data + //piCout << "received const data"; + state = Committing; + ba >> tabs; + cur_tab = tabs.isEmpty() ? -1 : 0; + piForeach (Tab & t, tabs) + piForeach (Column & c, t.columns) + piForeach (Variable & v, c.variables) + v.remote = true; + break; + case 0xDD: // const data commit + //piCout << "received commit"; + state = Connected; + break; + case 0xEE: // dynamic data + //piCout << "received data" << ba.size_s(); + piForeach (Tab & t, tabs) + piForeach (Column & c, t.columns) + piForeach (Variable & v, c.variables) + if (!v.isEmpty() && v.id > 0) + vids[v.id] = &v; + ba >> content; + piForeach (VariableContent & vc, content) { + if (vc.id <= 0) continue; + Variable * v = vids.at(vc.id); + if (v == 0) continue; + //piCout << "read" << v->name << vc.rdata.size_s(); + v->rdata = vc.rdata; + } + break; + default: break; + } + } +} + + +void PIConsole::peerTimer(void * data, int delim) { + if (peer == 0) return; + //piCout << "timer" << delim; + if (server_mode) { + if (delim == 20) + serverSendInfo(); + else + serverSendData(); + } else { + if (delim != 1 || server_name.isEmpty()) return; + const PIPeer::PeerInfo * p = peer->getPeerByName("_rcs_:" + server_name); + if (p == 0) return; + PIByteArray ba; + switch (state) { + case Disconnected: + peer_tm.reset(); + ba << int(0xBB); + //piCout << "send to" << server_name << "fetch request disc"; + peer->send(p, ba); + state = FetchingData; + break; + case FetchingData: + if (peer_tm.elapsed_s() < 3.) + return; + peer_tm.reset(); + ba << int(0xBB); + //piCout << "send to" << server_name << "fetch request fd"; + peer->send(p, ba); + break; + case Committing: + peer_tm.reset(); + ba << int(0xCC); + //piCout << "send to" << server_name << "committing"; + state = Connected; + peer->send(p, ba); + break; + default: break; + }; + } +} + + +void PIConsole::peerDisconnectedEvent(const PIString & name) { + for (int i = 0; i < remote_clients.size_s(); ++i) + if (remote_clients[i].name == name) { + remote_clients.remove(i); + --i; + } +} diff --git a/src/console/piconsole.h b/src/console/piconsole.h new file mode 100644 index 00000000..ee10bfe6 --- /dev/null +++ b/src/console/piconsole.h @@ -0,0 +1,486 @@ +/*! \file piconsole.h + * \brief Console output class +*/ +/* + 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 . +*/ + +#ifndef PICONSOLE_H +#define PICONSOLE_H + +#include "pikbdlistener.h" +#include "pitimer.h" + +class PIProtocol; +class PIDiagnostics; +class PISystemMonitor; +class PIPeer; + +class PIP_EXPORT PIConsole: public PIThread +{ + PIOBJECT_SUBCLASS(PIConsole, PIThread) +public: + + //! Constructs %PIConsole with key handler "slot" and if "startNow" start it + explicit PIConsole(bool startNow = true, PIKbdListener::KBFunc slot = 0); + + ~PIConsole(); + + + //! Variables output format + enum Format { + Normal /** Default console format */ = 0x01, + Bold /** Bold text */ = 0x02, + Faint = 0x04, + Italic = 0x08, + Underline /** Underlined text */ = 0x10, + Blink /** Blinked text */ = 0x20, + Inverse /** Swap text and background colors */ = 0x40, + Black /** Black text */ = 0x100, + Red /** Red text */ = 0x200, + Green /** Green text */ = 0x400, + Yellow /** Yellow text */ = 0x800, + Blue /** Blue text */ = 0x1000, + Magenta /** Magenta text */ = 0x2000, + Cyan /** Cyan text */ = 0x4000, + White /** White text */ = 0x8000, + BackBlack /** Black background */ = 0x10000, + BackRed /** Red background */ = 0x20000, + BackGreen /** Green background */ = 0x40000, + BackYellow /** Yellow background */ = 0x80000, + BackBlue /** Blue background */ = 0x100000, + BackMagenta /** Magenta background */ = 0x200000, + BackCyan /** Cyan background */ = 0x400000, + BackWhite /** White background */ = 0x800000, + Dec /** Decimal base for integers */ = 0x1000000, + Hex /** Hexadecimal base for integers */ = 0x2000000, + Oct /** Octal base for integers */ = 0x4000000, + Bin /** Binary base for integers */ = 0x8000000, + Scientific /** Scientific representation of floats */ = 0x10000000, + SystemTimeSplit /** PISystemTime split representation (* s, * ns) */ = 0x20000000, + SystemTimeSeconds /** PISystemTime seconds representation (*.* s) */ = 0x40000000 + }; + + //! Column labels alignment + enum Alignment { + Nothing /** No alignment */ , + Left /** Labels align left and variables align left */ , + Right /** Labels align right and variables align left */ + }; + + typedef PIFlags FormatFlags; + + //! Add to current tab to column "column" string "name" with format "format" + void addString(const PIString & name, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + void addVariable(const PIString & name, const PIString * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + void addVariable(const PIString & name, const char * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + void addVariable(const PIString & name, const bool * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + void addVariable(const PIString & name, const short * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + void addVariable(const PIString & name, const int * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + void addVariable(const PIString & name, const long * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + void addVariable(const PIString & name, const llong * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + void addVariable(const PIString & name, const uchar * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + void addVariable(const PIString & name, const ushort * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + void addVariable(const PIString & name, const uint * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + void addVariable(const PIString & name, const ulong * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + void addVariable(const PIString & name, const ullong * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + void addVariable(const PIString & name, const float * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + void addVariable(const PIString & name, const double * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" variable with label "name", pointer "ptr" and format "format" + void addVariable(const PIString & name, const PISystemTime * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + + void addVariable(const PIString & name, const PIProtocol * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const PIDiagnostics * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const PISystemMonitor * ptr, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" bits field with label "name", pointer "ptr" and format "format" + void addBitVariable(const PIString & name, const void * ptr, int fromBit, int bitsCount, int column = 1, FormatFlags format = PIConsole::Normal); + + //! Add to current tab to column "column" "count" empty lines + void addEmptyLine(int column = 1, uint count = 1); + + PIString getString(int x, int y); + short getShort(int x, int y) {return getString(x, y).toShort();} + int getInt(int x, int y) {return getString(x, y).toInt();} + float getFloat(int x, int y) {return getString(x, y).toFloat();} + double getDouble(int x, int y) {return getString(x, y).toDouble();} + PIString getString(const PIString & name); + short getShort(const PIString & name) {return getString(name).toShort();} + int getInt(const PIString & name) {return getString(name).toInt();} + float getFloat(const PIString & name) {return getString(name).toFloat();} + double getDouble(const PIString & name) {return getString(name).toDouble();} + + + //! Returns tabs count + uint tabsCount() const {return tabs.size();} + + //! Returns current tab name + PIString currentTab() const {return tabs[cur_tab].name;} + + //! Returns current tab index + int currentTabIndex() const {return cur_tab;} + + //! Add new tab with name "name", bind key "bind_key" and returns this tab index + int addTab(const PIString & name, char bind_key = 0); + + //! Remove tab with index "index" + void removeTab(uint index); + + //! Remove tab with name "name" + void removeTab(const PIString & name); + + //! Clear content of tab with index "index" + void clearTab(uint index); + + //! Clear content of tab with name "name" + void clearTab(const PIString & name); + + //! Set current tab to tab with index "index", returns if tab exists + bool setTab(uint index); + + //! Set current tab to tab with name "name", returns if tab exists + bool setTab(const PIString & name); + + //! Set tab with index "index" bind key to "bind_key", returns if tab exists + bool setTabBindKey(uint index, char bind_key); + + //! Set tab with name "name" bind key to "bind_key", returns if tab exists + bool setTabBindKey(const PIString & name, char bind_key); + + //! Remove all tabs and if "clearScreen" clear the screen + void clearTabs(bool clearScreen = true) {if (clearScreen && isRunning()) {toUpperLeft(); clearScreenLower();} tabs.clear();} + + + //! Set custom status text of current tab to "str" + void addCustomStatus(const PIString & str) {tabs[cur_tab].status = str;} + + //! Clear custom status text of current tab + void clearCustomStatus() {tabs[cur_tab].status.clear();} + + //! Returns default alignment + Alignment defaultAlignment() const {return def_align;} + + //! Set default alignment to "align" + void setDefaultAlignment(Alignment align) {def_align = align;} + + //! Set column "col" alignment to "align" + void setColumnAlignment(int col, Alignment align) {if (col < 0 || col >= columns().size_s()) return; column(col).alignment = align;} + + //! Set all columns of all tabs alignment to "align" + void setColumnAlignmentToAll(Alignment align) {piForeach (Tab & i, tabs) piForeach (Column & j, i.columns) j.alignment = align; fillLabels();} + + + //! Directly call function from \a PIKbdListener + void enableExitCapture(char key = 'Q') {listener->enableExitCapture(key);} + + //! Directly call function from \a PIKbdListener + void disableExitCapture() {listener->disableExitCapture();} + + //! Directly call function from \a PIKbdListener + bool exitCaptured() const {return listener->exitCaptured();} + + //! Directly call function from \a PIKbdListener + char exitKey() const {return listener->exitKey();} + + + int windowWidth() const {return width;} + int windowHeight() const {return height;} + + PIString fstr(FormatFlags f); + void update(); + void pause(bool yes) {pause_ = yes;} + + // Server functions + void startServer(const PIString & name); + void stopPeer(); + bool isServerStarted() const {return peer != 0;} + PIStringList clients() const; + + // Client functions + void listenServers(); + PIStringList availableServers() const; + PIString selectedServer() const {return server_name;} + void connectToServer(const PIString & name); + void disconnect(); + bool isConnected() const {return state == Connected;} + +#ifdef WINDOWS + void toUpperLeft(); + void moveRight(int n = 1); + void moveLeft(int n = 1); + void moveTo(int x = 0, int y = 0); + void clearScreen(); + void clearScreenLower(); + void clearLine(); + void newLine(); + void hideCursor(); + void showCursor(); +#else + void toUpperLeft() {printf("\e[H");} + void moveRight(int n = 1) {if (n > 0) printf("\e[%dC", n);} + void moveLeft(int n = 1) {if (n > 0) printf("\e[%dD", n);} + void moveTo(int x = 0, int y = 0) {printf("\e[%d;%dH", y, x);} + void clearScreen() {couts(fstr(Normal)); printf("\e[H\e[J");} + void clearScreenLower() {couts(fstr(Normal)); printf("\e[J");} + void clearLine() {printf("\e[K");} + void newLine() {printf("\eE");} + void hideCursor() {printf("\e[?25l");} + void showCursor() {printf("\e[?25h");} +#endif + + EVENT_HANDLER0(void, clearVariables) {clearVariables(true);} + EVENT_HANDLER1(void, clearVariables, bool, clearScreen); + + EVENT_HANDLER0(void, waitForFinish) {WAIT_FOR_EXIT} + EVENT_HANDLER0(void, start) {start(false);} + EVENT_HANDLER1(void, start, bool, wait) {PIThread::start(40); if (wait) waitForFinish();} + EVENT_HANDLER0(void, stop) {stop(false);} + EVENT_HANDLER1(void, stop, bool, clear); + + EVENT2(keyPressed, PIKbdListener::KeyEvent, key, void * , data) + +//! \handlers +//! \{ + + //! \fn void waitForFinish() + //! \brief block until finished (exit key will be pressed) + + //! \fn void clearVariables(bool clearScreen = true) + //! \brief Remove all columns at current tab and if "clearScreen" clear the screen + + //! \fn void start(bool wait = false) + //! \brief Start console output and if "wait" block until finished (exit key will be pressed) + + //! \fn void stop(bool clear = false) + //! \brief Stop console output and if "clear" clear the screen + +//! \} +//! \events +//! \{ + + //! \fn void keyPressed(PIKbdListener::KeyEvent key, void * data) + //! \brief Raise on key "key" pressed, "data" is pointer to %PIConsole object + +//! \} + +private: + void begin(); + void run(); + void fillLabels(); + void status(); + void checkColumn(uint col) {while (columns().size() < col) columns().push_back(Column(def_align));} + int bitsValue(const void * src, int offset, int count) const; + const char * toBin(const void * d, int s); + inline void printLine(const PIString & str, int dx = 0, FormatFlags format = PIConsole::Normal); + inline int printValue(const PIString & str, FormatFlags format = PIConsole::Normal); + inline int printValue(const char * str, FormatFlags format = PIConsole::Normal); + inline int printValue(const bool value, FormatFlags format = PIConsole::Normal); + inline int printValue(const int value, FormatFlags format = PIConsole::Normal); + inline int printValue(const long value, FormatFlags format = PIConsole::Normal); + inline int printValue(const llong value, FormatFlags format = PIConsole::Normal); + inline int printValue(const float value, FormatFlags format = PIConsole::Normal); + inline int printValue(const double value, FormatFlags format = PIConsole::Normal); + inline int printValue(const char value, FormatFlags format = PIConsole::Normal); + inline int printValue(const short value, FormatFlags format = PIConsole::Normal); + inline int printValue(const uchar value, FormatFlags format = PIConsole::Normal); + inline int printValue(const ushort value, FormatFlags format = PIConsole::Normal); + inline int printValue(const uint value, FormatFlags format = PIConsole::Normal); + inline int printValue(const ulong value, FormatFlags format = PIConsole::Normal); + inline int printValue(const ullong value, FormatFlags format = PIConsole::Normal); + inline int printValue(const PISystemTime & value, FormatFlags format = PIConsole::Normal); + static void key_event(PIKbdListener::KeyEvent key, void * t); + + struct Variable { + Variable() {nx = ny = type = offset = bitFrom = bitCount = size = 0; format = Normal; remote = false; ptr = 0; id = 1;} + bool isEmpty() const {return (remote ? false : ptr == 0);} + const void * data() {return (remote ? rdata.data() : ptr);} + void writeData(PIByteArray & ba) { + if (remote) ba << rdata; + else { + if (type == 0) ba << (*(PIString * )ptr); + else ba << PIByteArray::RawData(ptr, size); + } + } + PIString name; + FormatFlags format; + int nx; + int ny; + int type; + int offset; + int bitFrom; + int bitCount; + int size; + int id; + bool remote; + const void * ptr; + PIByteArray rdata; + void operator =(const Variable & src) {remote = src.remote; name = src.name; format = src.format; type = src.type; offset = src.offset; size = src.size; + bitFrom = src.bitFrom; bitCount = src.bitCount; ptr = src.ptr; nx = src.nx; ny = src.ny; rdata = src.rdata; id = src.id;} + }; + + struct VariableContent { + int id; + PIByteArray rdata; + }; + + struct Column { + Column(Alignment align = PIConsole::Right) {variables.reserve(32); alignment = align;} + PIVector variables; + Alignment alignment; + uint size() const {return variables.size();} + Variable & operator [](int index) {return variables[index];} + const Variable & operator [](int index) const {return variables[index];} + void push_back(const Variable & v) {variables.push_back(v);} + void operator =(const Column & src) {variables = src.variables; alignment = src.alignment;} + }; + + struct Tab { + Tab(PIString n = "", char k = 0) {columns.reserve(8); name = n; key = k;} + PIVector columns; + PIString name; + PIString status; + char key; + }; + + enum ConnectedState {Disconnected, FetchingData, Committing, Connected}; + + friend PIByteArray & operator <<(PIByteArray & ba, const PIConsole::VariableContent & v); + friend PIByteArray & operator >>(PIByteArray & ba, PIConsole::VariableContent & v); + + friend PIByteArray & operator <<(PIByteArray & ba, const PIConsole::Variable & v); + friend PIByteArray & operator >>(PIByteArray & ba, PIConsole::Variable & v); + + friend PIByteArray & operator <<(PIByteArray & ba, const PIConsole::Column & v); + friend PIByteArray & operator >>(PIByteArray & ba, PIConsole::Column & v); + + friend PIByteArray & operator <<(PIByteArray & ba, const PIConsole::Tab & v); + friend PIByteArray & operator >>(PIByteArray & ba, PIConsole::Tab & v); + + PIVector & columns() {return tabs[cur_tab].columns;} + Column & column(int index) {return tabs[cur_tab].columns[index - 1];} + inline int couts(const PIString & v) {return printf("%s", v.data());} + inline int couts(const char * v) {return printf("%s", v);} + inline int couts(const bool v); + inline int couts(const char v); + inline int couts(const short v); + inline int couts(const int v); + inline int couts(const long v); + inline int couts(const llong v); + inline int couts(const uchar v); + inline int couts(const ushort v); + inline int couts(const uint v); + inline int couts(const ulong v); + inline int couts(const ullong v); + inline int couts(const float v); + inline int couts(const double v); + inline int couts(const PISystemTime & v); + + struct RemoteClient; + + void serverSendInfo(); + void serverSendData(); + RemoteClient & remoteClient(const PIString & fname); + EVENT_HANDLER2(void, peerReceived, const PIString &, from, const PIByteArray &, data); + EVENT_HANDLER2(void, peerTimer, void * , data, int, delim); + EVENT_HANDLER1(void, peerDisconnectedEvent, const PIString &, name); + + PRIVATE_DECLARATION + PIVector tabs; + PIString binstr, rstr; + PIByteArray rba; + Variable tv; + PIKbdListener * listener; + Alignment def_align; + PIKbdListener::KBFunc ret_func; + int width, height, pwidth, pheight, ret, col_wid, num_format, systime_format; + uint max_y; + int vid; + uint cur_tab, col_cnt; + + PIPeer * peer; + PITimer peer_timer; + PITimeMeasurer peer_tm; + PIString server_name; + bool server_mode, pause_; + ConnectedState state; + + /*struct RemoteData { + RemoteData() {msg_count = msg_rec = msg_send = 0;} + void clear() {msg_count = msg_rec = msg_send = 0; data.clear();} + bool isEmpty() const {return msg_count == 0;} + bool isReadyRec() const {return msg_count == msg_rec;} + bool isReadySend() const {return msg_count == msg_send;} + void setData(const PIByteArray & ba) {data = ba; msg_rec = msg_send = 0; msg_count = (data.size_s() - 1) / 4096 + 1;} + PIByteArray data; + int msg_count; + int msg_rec; + int msg_send; + };*/ + + struct RemoteClient { + RemoteClient(const PIString & n = "") {name = n; state = Disconnected;} + PIString name; + ConnectedState state; + }; + + PIVector remote_clients; + +}; + +inline PIByteArray & operator <<(PIByteArray & ba, const PIConsole::VariableContent & v) {ba << v.id << v.rdata; return ba;} +inline PIByteArray & operator >>(PIByteArray & ba, PIConsole::VariableContent & v) {ba >> v.id; ba >> v.rdata; return ba;} + +inline PIByteArray & operator <<(PIByteArray & ba, const PIConsole::Variable & v) {ba << v.name << v.id << (int)v.format << v.type << v.size << v.bitFrom << v.bitCount; return ba;} +inline PIByteArray & operator >>(PIByteArray & ba, PIConsole::Variable & v) {ba >> v.name >> v.id >> (int & )v.format >> v.type >> v.size >> v.bitFrom >> v.bitCount; return ba;} + +inline PIByteArray & operator <<(PIByteArray & ba, const PIConsole::Column & v) {ba << (int)v.alignment << v.variables; return ba;} +inline PIByteArray & operator >>(PIByteArray & ba, PIConsole::Column & v) {ba >> (int & )v.alignment >> v.variables; return ba;} + +inline PIByteArray & operator <<(PIByteArray & ba, const PIConsole::Tab & v) {ba << v.name << v.status << (uchar)v.key << v.columns; return ba;} +inline PIByteArray & operator >>(PIByteArray & ba, PIConsole::Tab & v) {ba >> v.name >> v.status >> (uchar&)v.key >> v.columns; return ba;} + +#endif // PICONSOLE_H diff --git a/src/console/piconsolemodule.h b/src/console/piconsolemodule.h new file mode 100644 index 00000000..4b4c397c --- /dev/null +++ b/src/console/piconsolemodule.h @@ -0,0 +1,28 @@ +/* + PIP - Platform Independent Primitives + Module includes + 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 . +*/ + +#ifndef PICONSOLEMODULE_H +#define PICONSOLEMODULE_H + +#include "pikbdlistener.h" +#include "piconsole.h" +#include "piscreen.h" +#include "piscreentiles.h" + +#endif // PICONSOLEMODULE_H diff --git a/src/console/pikbdlistener.cpp b/src/console/pikbdlistener.cpp new file mode 100644 index 00000000..44eb40cd --- /dev/null +++ b/src/console/pikbdlistener.cpp @@ -0,0 +1,297 @@ +/* + PIP - Platform Independent Primitives + Keyboard grabber for console + 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 "pikbdlistener.h" +#ifndef WINDOWS +# include +#else +# include +#endif + + +/** \class PIKbdListener + * \brief Keyboard console input listener + * \details This class provide listening of console keyboard input. + * There is two ways to receive pressed key: + * * external static function with format "void func(char key, void * data_)" + * * event \a keyPressed() + * + * Also there is static variable \a exiting which by default is set to + * \b false. If \a enableExitCapture() was called and listener was started + * with function \a start(), this variable will be set to \b true if exit + * key will be pressed. By default exit key is 'Q' = shift + 'q'. + * To wait for this variable changes to \b true there is WAIT_FOR_EXIT macro + * \snippet pikbdlistener.cpp main + * */ + +bool PIKbdListener::exiting; + + +#ifndef WINDOWS +struct EscSeq { + const char * seq; + int key; +}; +const EscSeq esc_seq[] = { + // Linux + {"[A", PIKbdListener::UpArrow}, + {"[1A", PIKbdListener::UpArrow}, + {"[B", PIKbdListener::DownArrow}, + {"[1B", PIKbdListener::DownArrow}, + {"[C", PIKbdListener::RightArrow}, + {"[1C", PIKbdListener::RightArrow}, + {"[D", PIKbdListener::LeftArrow}, + {"[1D", PIKbdListener::LeftArrow}, + {"[H", PIKbdListener::Home}, + {"[1H", PIKbdListener::Home}, + {"[1~", PIKbdListener::Home}, + {"[F", PIKbdListener::End}, + {"[1F", PIKbdListener::End}, + {"[4~", PIKbdListener::End}, + {"[2~", PIKbdListener::Insert}, + {"[3~", PIKbdListener::Delete}, + {"[5~", PIKbdListener::PageUp}, + {"[6~", PIKbdListener::PageDown}, + {"OP", PIKbdListener::F1}, + {"[[A", PIKbdListener::F1}, + {"OQ", PIKbdListener::F2}, + {"[[B", PIKbdListener::F2}, + {"OR", PIKbdListener::F3}, + {"[[C", PIKbdListener::F3}, + {"OS", PIKbdListener::F4}, + {"[[D", PIKbdListener::F4}, + {"[[E", PIKbdListener::F5}, + {"[15~", PIKbdListener::F5}, + {"[17~", PIKbdListener::F6}, + {"[18~", PIKbdListener::F7}, + {"[19~", PIKbdListener::F8}, + {"[20~", PIKbdListener::F9}, + {"[21~", PIKbdListener::F10}, + {"[23~", PIKbdListener::F11}, + {"[24~", PIKbdListener::F12}, + + // End + {0, 0}, + +}; +#endif + + +PRIVATE_DEFINITION_START(PIKbdListener) +#ifdef WINDOWS + void * hIn; + DWORD smode, tmode; +#else + struct termios sterm, tterm; +#endif +PRIVATE_DEFINITION_END(PIKbdListener) + + +PIKbdListener::PIKbdListener(KBFunc slot, void * _data): PIThread() { + setName("keyboard_listener"); +#ifdef WINDOWS + PRIVATE->hIn = GetStdHandle(STD_INPUT_HANDLE); + GetConsoleMode(PRIVATE->hIn, &PRIVATE->smode); +#else + tcgetattr(0, &PRIVATE->sterm); +#endif + is_active = true; + ret_func = slot; + data_ = _data; + PIKbdListener::exiting = exit_enabled = false; + start(); +} + + +PIKbdListener::~PIKbdListener() { + terminate(); + end(); +} + + +void PIKbdListener::begin() { +#ifdef WINDOWS + GetConsoleMode(PRIVATE->hIn, &PRIVATE->tmode); + SetConsoleMode(PRIVATE->hIn, ENABLE_PROCESSED_INPUT); +#else + struct termios term; + tcgetattr(0, &term); + term.c_lflag &= ~(ECHO | ICANON); + term.c_lflag |= NOFLSH | IEXTEN; + PRIVATE->tterm = term; + tcsetattr(0, TCSANOW, &term); +#endif +} + + +void PIKbdListener::run() { + ke.key = 0; + ke.modifiers = 0; + memset(rc, 0, 8); +#ifdef WINDOWS + INPUT_RECORD ir; + ReadConsoleInput(PRIVATE->hIn, &ir, 1, &ret); + if (ir.EventType == KEY_EVENT) { + KEY_EVENT_RECORD ker = ir.Event.KeyEvent; + if (ker.bKeyDown) { + bool ctrl = ker.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED); + bool shift = ker.dwControlKeyState & SHIFT_PRESSED; + bool alt = ker.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED); + if (ctrl) ke.modifiers |= Ctrl; + if (shift) ke.modifiers |= Shift; + if (alt) ke.modifiers |= Alt; + //if (meta) ke.modifiers |= Meta; + if (ker.dwControlKeyState & CAPSLOCK_ON) shift = !shift; + //cout << "key " << int(ker.wVirtualKeyCode) << endl; + switch (ker.wVirtualKeyCode) { + case 8: ret = 1; ke.key = Backspace; break; + case 33: ret = 1; ke.key = PageUp; break; + case 34: ret = 1; ke.key = PageDown; break; + case 35: ret = 1; ke.key = End; break; + case 36: ret = 1; ke.key = Home; break; + case 37: ret = 1; ke.key = LeftArrow; break; + case 38: ret = 1; ke.key = UpArrow; break; + case 39: ret = 1; ke.key = RightArrow; break; + case 40: ret = 1; ke.key = DownArrow; break; + case 45: ret = 1; ke.key = Insert; break; + case 46: ret = 1; ke.key = Delete; break; + case 112: ret = 1; ke.key = F1; break; + case 113: ret = 1; ke.key = F2; break; + case 114: ret = 1; ke.key = F3; break; + case 115: ret = 1; ke.key = F4; break; + case 116: ret = 1; ke.key = F5; break; + case 117: ret = 1; ke.key = F6; break; + case 118: ret = 1; ke.key = F7; break; + case 119: ret = 1; ke.key = F8; break; + case 120: ret = 1; ke.key = F9; break; + case 121: ret = 1; ke.key = F10; break; + case 122: ret = 1; ke.key = F11; break; + case 123: ret = 1; ke.key = F12; break; + default: ret = 1; ke.key = rc[0] = (shift ? char(toupper(ker.uChar.AsciiChar)) : ker.uChar.AsciiChar); break; + } + if (ke.key == 0) {piMSleep(10); return;} + } else {piMSleep(10); return;} + } else {piMSleep(10); return;} + /*if (lc == 0) { + ReadConsole(hIn, &rc, 1, &ret, 0); + //cout << "read console" << endl; + lc = char(rc); + }*/ + /*if (ret < 0 || ret > 3) return; + lc = char(((uchar * )&rc)[ret - 1]); + for (int i = 0; i < ret; ++i) + cout << std::hex << int(((uchar * )&rc)[i]) << ' '; + cout << endl << std::hex << rc << endl;*/ +#else + tcsetattr(0, TCSANOW, &PRIVATE->tterm); + ret = read(0, rc, 8); + /*piCout << NewLine << "read" << ret; + for (int i = 0; i < ret; ++i) + cout << std::hex << int(((uchar * )&rc)[i]) << ' '; + cout << endl; + for (int i = 0; i < ret; ++i) + cout << "'" << (char)(rc[i]) << "' "; + cout << endl;*/ + if (rc[0] == 0) {piMSleep(10); return;} + if (ret < 0 || ret > 7) {piMSleep(10); return;} + if (ret == 1) ke.key = rc[0]; + int mod(0); + // 2 - shift 1 + // 3 - alt 2 + // 4 - alt+shift 3 + // 5 - ctrl 4 + // 8 - ctrl+alt+shift 7 + if (rc[0] == '\e' && ret >= 2) { // escape-seq + if (rc[1] == '[') { + for (int i = 2; i < 7; ++i) // search for modifier + if (rc[i] == ';') { + mod = rc[i + 1] - '0' - 1; + for (int j = i; j < 6; ++j) rc[j] = rc[j + 2]; + rc[6] = rc[7] = 0; + ret -= 2; + break; + } + } + if (ret >= 3 && rc[1] == 'O') { // search for modifier (F1-F4) + if (rc[2] >= '1' && rc[2] <= '8') { + mod = rc[2] - '0' - 1; + for (int j = 2; j < 6; ++j) rc[j] = rc[j + 1]; + rc[7] = 0; + ret -= 1; + } + } + if (mod >= 0 && mod <= 15) { + if (mod & 0x1) ke.modifiers |= Shift; + if (mod & 0x2) ke.modifiers |= Alt; + if (mod & 0x4) ke.modifiers |= Ctrl; + //if (mod & 0x8) ke.modifiers |= Meta; + } + /*cout << "wo mods (" << mod << ")\n"; + for (int i = 0; i < ret; ++i) + cout << "'" << (char)(rc[i]) << "' "; + cout << endl; + }*/ + for (int i = 0; ; ++i) { + if (!esc_seq[i].seq) break; + if (strcmp(esc_seq[i].seq, &(rc[1])) == 0) { + ke.key = esc_seq[i].key; + break; + } + } + } +#endif + if ((rc[0] == '\n' || rc[0] == '\r') && ret == 1) + ke.key = Return; + if (exit_enabled && ke.key == exit_key) { + PIKbdListener::exiting = true; + return; + } + if (ret > 0) { + keyPressed(ke, data_); + if (ret_func != 0) ret_func(ke, data_); + } +} + + +void PIKbdListener::end() { + //cout << "list end" << endl; +#ifdef WINDOWS + SetConsoleMode(PRIVATE->hIn, PRIVATE->smode); +#else + tcsetattr(0, TCSANOW, &PRIVATE->sterm); +#endif +} + + +void PIKbdListener::setActive(bool yes) { + is_active = yes; + if (is_active) { +#ifdef WINDOWS + SetConsoleMode(PRIVATE->hIn, PRIVATE->tmode); +#else + tcsetattr(0, TCSANOW, &PRIVATE->tterm); +#endif + } else { +#ifdef WINDOWS + SetConsoleMode(PRIVATE->hIn, PRIVATE->smode); +#else + tcsetattr(0, TCSANOW, &PRIVATE->sterm); +#endif + } +} diff --git a/src/console/pikbdlistener.h b/src/console/pikbdlistener.h new file mode 100644 index 00000000..dfdeea12 --- /dev/null +++ b/src/console/pikbdlistener.h @@ -0,0 +1,169 @@ +/*! \file pikbdlistener.h + * \brief Keyboard console input listener +*/ +/* + PIP - Platform Independent Primitives + Keyboard grabber for console + 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 . +*/ + +#ifndef PIKBDLISTENER_H +#define PIKBDLISTENER_H + +#include "pithread.h" + +#define WAIT_FOR_EXIT while (!PIKbdListener::exiting) piMSleep(5); + + +class PIP_EXPORT PIKbdListener: public PIThread +{ + PIOBJECT_SUBCLASS(PIKbdListener, PIThread) + friend class PIConsole; +public: + + //! Special keyboard keys + enum SpecialKey { + Tab /** Tab key */ = 0x09, + Return /** Enter key */ = 0x0a, + Esc /** Escape key */ = 0x1b, + Space /** Space key */ = 0x20, + Backspace /** Backspace key */ = 0x7f, + UpArrow /** Up arrow key */ = -1, + DownArrow /** Down arrow key */ = -2, + RightArrow /** Right arrow key */ = -3, + LeftArrow /** Left arrow key */ = -4, + Home /** Home key */ = -5, + End /** End key */ = -6, + PageUp /** Page up key */ = -7, + PageDown /** Page down key */ = -8, + Insert /** Delete key */ = -9, + Delete /** Delete key */ = -10, + F1 /** F1 key */ = -11, + F2 /** F2 key */ = -12, + F3 /** F3 key */ = -13, + F4 /** F4 key */ = -14, + F5 /** F5 key */ = -15, + F6 /** F6 key */ = -16, + F7 /** F7 key */ = -17, + F8 /** F8 key */ = -18, + F9 /** F9 key */ = -19, + F10 /** F10 key */ = -20, + F11 /** F11 key */ = -21, + F12 /** F12 key */ = -22 + }; + + //! Keyboard modifiers + enum KeyModifier { + Ctrl /** Control key */ = 0x1, + Shift /** Shift key */ = 0x2, + Alt /** Alt key */ = 0x4 + //Meta /** Meta (windows) key */ = 0x8 + }; + + typedef PIFlags KeyModifiers; + + //! This struct contains information about pressed keyboard key + struct KeyEvent { + KeyEvent(int k = 0, KeyModifiers m = 0) {key = k; modifiers = m;} + + //! Pressed key. It can be simple \b char or special key (see PIKbdListener::SpecialKey) + int key; + + //! Active keyboard modifiers. It contains PIKbdListener::KeyModifier bitfields + KeyModifiers modifiers; + }; + + typedef void (*KBFunc)(KeyEvent, void * ); + + + //! Constructs keyboard listener with external function "slot" and custom data "data" + explicit PIKbdListener(KBFunc slot = 0, void * data = 0); + + ~PIKbdListener(); + + + //! Returns custom data + void * data() {return data_;} + + //! Set custom data to "_data" + void setData(void * _data) {data_ = _data;} + + //! Set external function to "slot" + void setSlot(KBFunc slot) {ret_func = slot;} + + //! Returns if exit key if awaiting + bool exitCaptured() const {return exit_enabled;} + + //! Returns exit key, default 'Q' + char exitKey() const {return exit_key;} + + + //! Returns if keyboard listening is active (not running!) + bool isActive() {return is_active;} + + EVENT_HANDLER( void, enableExitCapture) {enableExitCapture('Q');} + EVENT_HANDLER1(void, enableExitCapture, int, key) {exit_enabled = true; exit_key = key;} + EVENT_HANDLER(void, disableExitCapture) {exit_enabled = false;} + EVENT_HANDLER(void, setActive) {setActive(true);} + EVENT_HANDLER1(void, setActive, bool, yes); + + EVENT2(keyPressed, KeyEvent, key, void * , data) + +//! \handlers +//! \{ + + //! \fn void enableExitCapture(int key = 'Q') + //! \brief Enable exit key "key" awaiting + + //! \fn void disableExitCapture() + //! \brief Disable exit key awaiting + + //! \fn void setActive(bool yes = true) + //! \brief Set keyboard listening is active or not + +//! \} +//! \events +//! \{ + + //! \fn void keyPressed(PIKbdListener::KeyEvent key, void * data) + //! \brief Raise on key "key" pressed, "data" is custom data + +//! \} + + static bool exiting; + +private: + void begin(); + void run(); + void end(); + + PRIVATE_DECLARATION +#ifdef WINDOWS + DWORD +#else + int +#endif + ret; + KBFunc ret_func; + int exit_key; + bool exit_enabled, is_active; + void * data_; + char rc[8]; + KeyEvent ke; + +}; + +#endif // PIKBDLISTENER_H diff --git a/src/console/piscreen.cpp b/src/console/piscreen.cpp new file mode 100644 index 00000000..96e0a6c5 --- /dev/null +++ b/src/console/piscreen.cpp @@ -0,0 +1,494 @@ +/* + 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]; + 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 tl = rt->children(), ftl; + piForeach (PIScreenTile * t, tl) { + if (t->focus_flags[CanHasFocus] && t->visible) + 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 && !ftl.isEmpty()) { + piForeach (PIScreenTile * t, tl) + t->has_focus = false; + 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; +} + diff --git a/src/console/piscreen.h b/src/console/piscreen.h new file mode 100644 index 00000000..08ace2b3 --- /dev/null +++ b/src/console/piscreen.h @@ -0,0 +1,157 @@ +/*! \file piscreen.h + * \brief Console output class +*/ +/* + 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 . +*/ + +#ifndef PISCREEN_H +#define PISCREEN_H + +#include "piscreentile.h" +#include "piscreendrawer.h" + + +class PIP_EXPORT PIScreen: public PIThread, public PIScreenTypes::PIScreenBase +{ + PIOBJECT_SUBCLASS(PIScreen, PIThread) + class SystemConsole; +public: + + //! Constructs %PIScreen with key handler "slot" and if "startNow" start it + PIScreen(bool startNow = true, PIKbdListener::KBFunc slot = 0); + + ~PIScreen(); + + //! Directly call function from \a PIKbdListener + void enableExitCapture(char key = 'Q') {listener->enableExitCapture(key);} + + //! Directly call function from \a PIKbdListener + void disableExitCapture() {listener->disableExitCapture();} + + //! Directly call function from \a PIKbdListener + bool exitCaptured() const {return listener->exitCaptured();} + + //! Directly call function from \a PIKbdListener + char exitKey() const {return listener->exitKey();} + + int windowWidth() const {return console.width;} + int windowHeight() const {return console.height;} + + PIScreenTile * rootTile() {return &root;} + PIScreenTile * tileByName(const PIString & name); + + void setDialogTile(PIScreenTile * t); + PIScreenTile * dialogTile() const {return tile_dialog;} + + PIScreenDrawer * drawer() {return &drawer_;} + void clear() {drawer_.clear();} + + EVENT_HANDLER0(void, waitForFinish); + EVENT_HANDLER0(void, start) {start(false);} + EVENT_HANDLER1(void, start, bool, wait) {PIThread::start(40); if (wait) waitForFinish();} + EVENT_HANDLER0(void, stop) {stop(false);} + EVENT_HANDLER1(void, stop, bool, clear); + + EVENT2(keyPressed, PIKbdListener::KeyEvent, key, void * , data) + EVENT2(tileEvent, PIScreenTile * , tile, PIScreenTypes::TileEvent, e) + +//! \handlers +//! \{ + + //! \fn void waitForFinish() + //! \brief block until finished (exit key will be pressed) + + //! \fn void clearVariables(bool clearScreen = true) + //! \brief Remove all columns at current tab and if "clearScreen" clear the screen + + //! \fn void start(bool wait = false) + //! \brief Start console output and if "wait" block until finished (exit key will be pressed) + + //! \fn void stop(bool clear = false) + //! \brief Stop console output and if "clear" clear the screen + +//! \} +//! \events +//! \{ + + //! \fn void keyPressed(PIKbdListener::KeyEvent key, void * data) + //! \brief Raise on key "key" pressed, "data" is pointer to %PIConsole object + +//! \} + +private: + class SystemConsole { + public: + SystemConsole(); + ~SystemConsole(); + void begin(); + void end(); + void prepare(); + void clear(); + void print(); + void resize(int w, int h); +#ifdef WINDOWS + void getWinCurCoord(); + void toUpperLeft(); + void moveRight(int n = 1); + void moveLeft(int n = 1); + void moveTo(int x = 0, int y = 0); + void clearScreen(); + void clearScreenLower(); + void clearLine(); + void newLine(); + void hideCursor(); + void showCursor(); + WORD attributes(const PIScreenTypes::Cell & c); +#else + void toUpperLeft() {printf("\e[H");} + void moveTo(int x = 0, int y = 0) {printf("\e[%d;%dH", y + 1, x + 1);} + void hideCursor() {printf("\e[?25l");} + void showCursor() {printf("\e[?25h");} + void clearScreen() {printf("\e[0m\e[H\e[J");} + void clearScreenLower() {printf("\e[0m\e[J");} + PIString formatString(const PIScreenTypes::Cell & c); +#endif + PRIVATE_DECLARATION + int width, height, pwidth, pheight; + PIVector > cells, pcells, dcells; + }; + + void begin(); + void run(); + void end(); + void key_event(PIKbdListener::KeyEvent key); + static void key_eventS(PIKbdListener::KeyEvent key, void * t) {((PIScreen*)t)->key_event(key);} + PIVector tiles() {return root.children();} + bool nextFocus(PIScreenTile * rt, PIKbdListener::KeyEvent key = PIKbdListener::KeyEvent()); + void tileEventInternal(PIScreenTile * t, PIScreenTypes::TileEvent e); + void tileRemovedInternal(PIScreenTile * t); + void tileSetFocusInternal(PIScreenTile * t); + + SystemConsole console; + PIScreenDrawer drawer_; + PIKbdListener * listener; + PIKbdListener::KBFunc ret_func; + PIScreenTile root; + PIScreenTile * tile_focus, * tile_dialog; + +}; + + +#endif // PISCREEN_H diff --git a/src/console/piscreendrawer.cpp b/src/console/piscreendrawer.cpp new file mode 100644 index 00000000..f0ee8f0c --- /dev/null +++ b/src/console/piscreendrawer.cpp @@ -0,0 +1,165 @@ +/* + 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 "piscreendrawer.h" + +/** \class PIScreenDrawer + * \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; + + +void PIScreenDrawer::clear() { + for (int i = 0; i < cells.size_s(); ++i) + cells[i].fill(Cell()); +} + + +void PIScreenDrawer::drawPixel(int x, int y, const PIChar & c, Color col_char, Color col_back, CharFlags flags_char) { + if (x < 0 || x >= width || y < 0 || y >= height) return; + cells[y][x].symbol = c; + cells[y][x].format.color_char = col_char; + cells[y][x].format.color_back = col_back; + cells[y][x].format.flags = flags_char; +} + + +void PIScreenDrawer::drawLine(int x0, int y0, int x1, int y1, const PIChar & c, Color col_char, Color col_back, CharFlags flags_char) { + if (x0 == x1 && y0 == y1) drawPixel(x0, y0, c, col_char, col_back, flags_char); + Cell cc; + cc.symbol = c; + cc.format.color_char = col_char; + cc.format.color_back = col_back; + cc.format.flags = flags_char; + int x = 0, y = 0; + if (piAbsi(x1 - x0) >= piAbsi(y1 - y0)) { + float dy = (y1 - y0) / float(piAbsi(x1 - x0)), cy = y0; + int dx = x0 < x1 ? 1 : -1; + for (int i = x0; i != x1; i += dx) { + x = i; y = piRound(cy); + if (x >= 0 && x < width && y >= 0 && y < height) + cells[y][x] = cc; + cy += dy; + } + y = piRound(cy); + if (x1 >= 0 && x1 < width && y >= 0 && y < height) + cells[y][x1] = cc; + } else { + float dx = (x1 - x0) / float(piAbsi(y1 - y0)), cx = x0; + int dy = y0 < y1 ? 1 : -1; + for (int i = y0; i != y1; i += dy) { + x = piRound(cx); y = i; + if (x >= 0 && x < width && y >= 0 && y < height) + cells[y][x] = cc; + cx += dx; + } + x = piRound(cx); + if (x >= 0 && x < width && y1 >= 0 && y1 < height) + cells[y1][x] = cc; + } +} + + +void PIScreenDrawer::drawRect(int x0, int y0, int x1, int y1, const PIChar & c, Color col_char, Color col_back, CharFlags flags_char) { + if (x0 == x1 && y0 == y1) drawPixel(x0, y0, c, col_char, col_back, flags_char); + Cell cc; + cc.symbol = c; + cc.format.color_char = col_char; + cc.format.color_back = col_back; + cc.format.flags = flags_char; + int dx = x0 < x1 ? 1 : -1; + int dy = y0 < y1 ? 1 : -1; + int xs[2] = {x0, x1}; + int ys[2] = {y0, y1}; + for (int k = 0; k < 2; ++k) { + int j = ys[k]; + if (j >= 0 && j < height) { + PIVector & cv(cells[j]); + for (int i = x0; i != x1; i += dx) + if (i >= 0 && i < width) + cv[i] = cc; + } + j = xs[k]; + if (j >= 0 && j < width) { + for (int i = y0; i != y1; i += dy) + if (i >= 0 && i < height) + cells[i][j] = cc; + } + } + int i = x1, j = y1; + if (i >= 0 && i < width && j >= 0 && j < height) + cells[j][i] = cc; +} + + +void PIScreenDrawer::fillRect(int x0, int y0, int x1, int y1, const PIChar & c, Color col_char, Color col_back, CharFlags flags_char) { + if (x0 == x1 && y0 == y1) drawPixel(x0, y0, c, col_char, col_back, flags_char); + Cell cc; + cc.symbol = c; + cc.format.color_char = col_char; + cc.format.color_back = col_back; + cc.format.flags = flags_char; + int dx = x0 < x1 ? 1 : -1; + int dy = y0 < y1 ? 1 : -1; + for (int j = y0; j != y1; j += dy) + if (j >= 0 && j < height) { + PIVector & cv(cells[j]); + for (int i = x0; i != x1; i += dx) + if (i >= 0 && i < width) + cv[i] = cc; + } +} + + +void PIScreenDrawer::drawText(int x, int y, const PIString & s, Color col_char, Color col_back, CharFlags flags_char) { + if (x < 0 || x >= width || y < 0 || y >= height) return; + PIVector & cv(cells[y]); + Cell cc; + cc.format.color_char = col_char; + cc.format.color_back = col_back; + cc.format.flags = flags_char; + for (int i = 0; i < s.size_s(); ++i) { + int j = i + x; + if (j >= 0 && j < width) { + cc.symbol = s[i]; + cv[j] = cc; + } + } +} diff --git a/src/console/piscreendrawer.h b/src/console/piscreendrawer.h new file mode 100644 index 00000000..a007104d --- /dev/null +++ b/src/console/piscreendrawer.h @@ -0,0 +1,49 @@ +/*! \file piscreendrawer.h + * \brief Drawer for PIScreen +*/ +/* + PIP - Platform Independent Primitives + Drawer for PIScreen + 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 . +*/ + +#ifndef PISCREENDRAWER_H +#define PISCREENDRAWER_H + +#include "piscreentypes.h" +#include "pistring.h" + +class PIScreenDrawer +{ + friend class PIScreen; + PIScreenDrawer(PIVector > & c): cells(c) {} +public: + void clear(); + void clearRect(int x0, int y0, int x1, int y1) {fillRect(x0, y0, x1, y1, ' ');} + void drawPixel(int x, int y, const PIChar & c, PIScreenTypes::Color col_char = PIScreenTypes::Default, PIScreenTypes::Color col_back = PIScreenTypes::Default, PIScreenTypes::CharFlags flags_char = 0); + void drawLine(int x0, int y0, int x1, int y1, const PIChar & c, PIScreenTypes::Color col_char = PIScreenTypes::Default, PIScreenTypes::Color col_back = PIScreenTypes::Default, PIScreenTypes::CharFlags flags_char = 0); + void drawRect(int x0, int y0, int x1, int y1, const PIChar & c, PIScreenTypes::Color col_char = PIScreenTypes::Default, PIScreenTypes::Color col_back = PIScreenTypes::Default, PIScreenTypes::CharFlags flags_char = 0); + void drawText(int x, int y, const PIString & s, PIScreenTypes::Color col_char = PIScreenTypes::Default, PIScreenTypes::Color col_back = PIScreenTypes::Transparent, PIScreenTypes::CharFlags flags_char = 0); + void fillRect(int x0, int y0, int x1, int y1, const PIChar & c, PIScreenTypes::Color col_char = PIScreenTypes::Default, PIScreenTypes::Color col_back = PIScreenTypes::Default, PIScreenTypes::CharFlags flags_char = 0); + +private: + PIVector > & cells; + int width, height; + +}; + + +#endif // PISCREENDRAWER_H diff --git a/src/console/piscreentile.cpp b/src/console/piscreentile.cpp new file mode 100644 index 00000000..dbceedef --- /dev/null +++ b/src/console/piscreentile.cpp @@ -0,0 +1,265 @@ +/* + PIP - Platform Independent Primitives + Basic PIScreen tile + 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 "piscreentile.h" +#include "piscreendrawer.h" + + +/** \class PIScreenTile + * \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; + + +PIScreenTile::PIScreenTile(const PIString & n, Direction d, SizePolicy p) { + name = n; + direction = d; + size_policy = p; + focus_flags = 0; + screen = 0; + minimumWidth = minimumHeight = x = y = width = height = pw = ph = 0; + maximumWidth = maximumHeight = 65535; + marginLeft = marginRight = marginTop = marginBottom = spacing = 0; + parent = 0; + back_symbol = ' '; + visible = true; + has_focus = false; +} + + +PIScreenTile::~PIScreenTile() { + //piCout << this << "~"; + if (screen) + screen->tileRemovedInternal(this); + setScreen(0); + deleteChildren(); + if (!parent) return; + parent->tiles.removeOne(this); +} + + +void PIScreenTile::addTile(PIScreenTile * t) { + if (t == 0) return; + if (tiles.contains(t)) return; + tiles << t; + t->parent = this; + t->setScreen(screen); +} + + +void PIScreenTile::takeTile(PIScreenTile * t) { + if (!tiles.contains(t)) return; + tiles.removeOne(t); + t->parent = 0; + t->setScreen(0); +} + + +void PIScreenTile::removeTile(PIScreenTile * t) { + if (!tiles.contains(t)) return; + tiles.removeOne(t); + t->parent = 0; + t->setScreen(0); + delete t; +} + + +PIVector PIScreenTile::children() { + PIVector ret; + piForeach (PIScreenTile * t, tiles) + ret << t << t->children(); + return ret; +} + + +void PIScreenTile::raiseEvent(TileEvent e) { + if (!screen) return; + screen->tileEventInternal(this, e); +} + + +void PIScreenTile::setScreen(PIScreenBase * s) { + screen = s; + piForeach (PIScreenTile * t, tiles) + t->setScreen(s); +} + + +void PIScreenTile::deleteChildren() { + //piCout << this << "deleteChildren"; + piForeach (PIScreenTile * t, tiles) { + //piCout << this << " child" << t; + //t->deleteChildren(); + t->parent = 0; + delete t; + } + tiles.clear(); +} + + +void PIScreenTile::setFocus() { + if (!screen || !focus_flags[CanHasFocus]) return; + screen->tileSetFocusInternal(this); +} + + +void PIScreenTile::drawEventInternal(PIScreenDrawer * d) { + if (!visible) { + //d->clearRect(x, y, x + width, y + height); + return; + } + d->fillRect(x, y, x + width, y + height, back_symbol, (Color)back_format.color_char, (Color)back_format.color_back, back_format.flags); + drawEvent(d); + piForeach (PIScreenTile * t, tiles) + t->drawEventInternal(d); +} + + +void PIScreenTile::sizeHint(int & w, int & h) const { + w = 0; + h = 0; + if (tiles.isEmpty()) return; + int sl = spacing * (tiles.size_s() - 1); + if (direction == Horizontal) w += sl; + else h += sl; + piForeachC (PIScreenTile * t, tiles) { + int cw(0), ch(0); + t->sizeHint(cw, ch); + cw = piClampi(cw, t->minimumWidth, t->maximumWidth); + ch = piClampi(ch, t->minimumHeight, t->maximumHeight); + if (direction == Horizontal) { + w += cw; h = piMaxi(h, ch); + } else { + h += ch; w = piMaxi(w, cw); + } + } + w += marginLeft + marginRight; + h += marginTop + marginBottom; +} + + +void PIScreenTile::layout() { + if (tiles.isEmpty() || !visible) return; + int as(0), ts(0), ts2(0), ecnt(0), pcnt(0); + ts = (direction == Horizontal) ? (width - marginLeft - marginRight) : (height - marginTop - marginBottom); + ts2 = (direction != Horizontal) ? (width - marginLeft - marginRight) : (height - marginTop - marginBottom); + ts -= spacing * (tiles.size_s() - 1); + PIVector hints(tiles.size_s()); + PIVector asizes(tiles.size_s()); + for (int i = 0; i < tiles.size_s(); ++i) { + PIScreenTile * t(tiles[i]); + int cw(0), ch(0), cs(0); + if (t->visible) { + t->sizeHint(cw, ch); + cw = piClampi(cw, t->minimumWidth, t->maximumWidth); + ch = piClampi(ch, t->minimumHeight, t->maximumHeight); + if (t->size_policy == Expanding) ++ecnt; + if (t->size_policy == Preferred) ++pcnt; + cs = (direction == Horizontal) ? cw : ch; + as += cs; + } + hints[i] = cs; + asizes[i] = 0.f; + } + if (as <= ts) { + int acnt(0); + SizePolicy pol; + if (ecnt > 0) { + acnt = ecnt; + pol = Expanding; + } else if (pcnt > 0) { + acnt = pcnt; + pol = Preferred; + } + if (acnt > 0) { + float add_a = float(ts - as), add_s = add_a / acnt, add_da(0.); + asizes.fill(add_s); + PISet max_tl; + for (int i = 0; i < tiles.size_s(); ++i) { + if (tiles[i]->size_policy == pol && tiles[i]->visible) { + float maxs = (direction == Horizontal) ? tiles[i]->maximumWidth : tiles[i]->maximumHeight; + if (hints[i] + asizes[i] > maxs) { + max_tl << i; + float pas = asizes[i]; + asizes[i] = maxs - hints[i]; + acnt--; + if (acnt > 0) { + pas = (pas - asizes[i]) / acnt; + for (int j = 0; j < tiles.size_s(); ++j) { + if (i == j) continue; + if (max_tl[j]) continue; + if (tiles[j]->size_policy == pol && tiles[j]->visible) + asizes[j] += pas; + } + } + } + } + } + for (int i = 0; i < tiles.size_s(); ++i) { + if (tiles[i]->size_policy == pol && tiles[i]->visible) { + int a = piRound(asizes[i] + add_da); + add_da += asizes[i] - a; + hints[i] += a; + } + } + } + } + int cx = x + marginLeft, cy = y + marginTop; + for (int i = 0; i < tiles.size_s(); ++i) { + PIScreenTile * t(tiles[i]); + if (!t->visible) continue; + t->x = cx; + t->y = cy; + if (direction == Horizontal) { + t->width = hints[i]; + t->height = ts2; + cx += hints[i] + spacing; + } else { + t->width = ts2; + t->height = hints[i]; + cy += hints[i] + spacing; + } + if (t->pw != t->width || t->ph != t->height) + t->resizeEvent(t->width, t->height); + t->pw = t->width; + t->ph = t->height; + t->layout(); + } +} diff --git a/src/console/piscreentile.h b/src/console/piscreentile.h new file mode 100644 index 00000000..04197a21 --- /dev/null +++ b/src/console/piscreentile.h @@ -0,0 +1,92 @@ +/*! \file piscreentile.h + * \brief Basic PIScreen tile +*/ +/* + PIP - Platform Independent Primitives + Basic PIScreen tile + 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 . +*/ + +#ifndef PISCREENTILE_H +#define PISCREENTILE_H + +#include "piscreentypes.h" +#include "pikbdlistener.h" + +class PIScreenDrawer; + +class PIScreenTile { + friend class PIScreen; +public: + PIScreenTile(const PIString & n = PIString(), PIScreenTypes::Direction d = PIScreenTypes::Vertical, PIScreenTypes::SizePolicy p = PIScreenTypes::Preferred); + virtual ~PIScreenTile(); + + void addTile(PIScreenTile * t); + void takeTile(PIScreenTile * t); + void removeTile(PIScreenTile * t); + PIScreenTile * parentTile() const {return parent;} + PIVector children(); + void show() {visible = true;} + void hide() {visible = false;} + void setFocus(); + void setMargins(int m) {marginLeft = marginRight = marginTop = marginBottom = m;} + void setMargins(int l, int r, int t, int b) {marginLeft = l; marginRight = r; marginTop = t; marginBottom = b;} + + PIString name; + PIScreenTypes::Direction direction; + PIScreenTypes::SizePolicy size_policy; + PIScreenTypes::FocusFlags focus_flags; + PIScreenTypes::CellFormat back_format; + PIChar back_symbol; + int minimumWidth, minimumHeight; + int maximumWidth, maximumHeight; + int marginLeft, marginRight, marginTop, marginBottom; + int spacing; + bool visible; + +protected: + + //! Returns desired tile size in "w" and "h" + virtual void sizeHint(int & w, int & h) const; + + //! Tile has been resized to "w"x"h" + virtual void resizeEvent(int w, int h) {} + + //! Draw tile with drawer "d" in world-space coordinates + virtual void drawEvent(PIScreenDrawer * d) {} + + //! Return "true" if you process key + virtual bool keyEvent(PIKbdListener::KeyEvent key) {return false;} + + void raiseEvent(PIScreenTypes::TileEvent e); + void setScreen(PIScreenTypes::PIScreenBase * s); + void deleteChildren(); + void drawEventInternal(PIScreenDrawer * d); + void layout(); + + PIVector tiles; + PIScreenTile * parent; + PIScreenTypes::PIScreenBase * screen; + int x, y, width, height; + bool has_focus; + +private: + int pw, ph; + +}; + + +#endif // PISCREENTILE_H diff --git a/src/console/piscreentiles.cpp b/src/console/piscreentiles.cpp new file mode 100644 index 00000000..1bd93eb7 --- /dev/null +++ b/src/console/piscreentiles.cpp @@ -0,0 +1,277 @@ +/* + PIP - Platform Independent Primitives + Various tiles for PIScreen + 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 "piscreentiles.h" +#include "piscreendrawer.h" + +/* + * \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; + + +TileSimple::TileSimple(const PIString & n): PIScreenTile(n) { + alignment = Left; +} + + +void TileSimple::sizeHint(int & w, int & h) const { + w = h = 0; + piForeachC (Row & r, content) + w = piMaxi(w, r.first.size_s()); + h = content.size_s(); +} + + +void TileSimple::drawEvent(PIScreenDrawer * d) { + for (int i = 0; i < content.size_s(); ++i) { + Row & r(content[i]); + int rx = 0; + switch (alignment) { + case Left: rx = x; break; + case Center: rx = x + (width - r.first.size_s()) / 2; break; + case Right: rx = x + width - r.first.size_s(); break; + }; + d->drawText(rx, y + i, r.first, (Color)r.second.color_char, (Color)r.second.color_back, r.second.flags); + } +} + + + + +TileList::TileList(const PIString & n): PIScreenTile(n) { + alignment = Left; + focus_flags = CanHasFocus | NextByArrows | NextByTab; + lhei = offset = cur = 0; + selection_mode = NoSelection; +} + + +void TileList::sizeHint(int & w, int & h) const { + w = h = 0; + piForeachC (Row & r, content) + w = piMaxi(w, r.first.size_s()); + h = 3; +} + + +void TileList::drawEvent(PIScreenDrawer * d) { + lhei = height - 2; + int is = piClampi(offset, 0, piMaxi(0, content.size_s() - 1)), ie = piClampi(offset + lhei, 0, content.size_s()); + if (is > 0) d->drawText(x, y, PIString(" /\\ ").repeat(width / 4), Green, Default, Bold); + for (int i = is; i < ie; ++i) { + Row & r(content[i]); + bool sel = i == cur && has_focus; + if (sel) { + int cy = y + i - is + 1; + d->drawLine(x, cy, x + width - 2, cy, ' ', Default, Blue); + } + int rx(0); + switch (alignment) { + case Left: rx = x; break; + case Center: rx = x + (width - 1 - r.first.size_s()) / 2; break; + case Right: rx = x + width - 1 - r.first.size_s(); break; + }; + CharFlags cf = r.second.flags; + Color cc = (Color)r.second.color_char; + if (selected[i]) { + cf |= Bold; + cc = Yellow; + } + d->drawText(rx, y + i - is + 1, r.first, cc, sel ? Blue : Default, cf); + } + int cx = x + width - 1; + d->drawLine(cx, y + 1, cx, y + height - 2, '|', Green); + if (content.size_s() > 1) + d->drawPixel(cx, y + piRound(float(cur) / (content.size_s() - 1) * (lhei - 1)) + 1, ' ', Green, Green); + if (ie < content.size_s()) d->drawText(x, y + height - 1, PIString(" \\/ ").repeat(width / 4), Green, Default, Bold); +} + + +bool TileList::keyEvent(PIKbdListener::KeyEvent key) { + lhei = height - 2; + int oo(0), osp = piMini(3, lhei / 4); + switch (key.key) { + case PIKbdListener::PageUp: + cur -= lhei / 2; + oo -= lhei / 2; + case PIKbdListener::UpArrow: + cur--; + oo--; + if (key.modifiers[PIKbdListener::Ctrl]) { + cur -= 4; + oo -= 4; + } + if (cur < 0) cur = 0; + if (cur - offset < osp) offset += oo; + if (offset < 0) offset = 0; + return true; + case PIKbdListener::Space: + if (cur < 0 || cur >= content.size_s()) return true; + switch (selection_mode) { + case NoSelection: return false; + case SingleSelection: + if (selected.isEmpty()) selected << cur; + else { + bool add = !selected[cur]; + selected.clear(); + if (add) selected << cur; + } + raiseEvent(TileEvent(SelectionChanged)); + return true; + case MultiSelection: + if (selected[cur]) selected.remove(cur); + else selected << cur; + raiseEvent(TileEvent(SelectionChanged)); + break; + } + case PIKbdListener::PageDown: + if (key.key == PIKbdListener::PageDown) { + cur += lhei / 2; + oo += lhei / 2; + } + case PIKbdListener::DownArrow: + cur++; + oo++; + if (key.modifiers[PIKbdListener::Ctrl]) { + cur += 4; + oo += 4; + } + if (cur >= content.size_s()) cur = content.size_s() - 1; + if (cur - offset >= lhei - osp) offset += oo; + if (offset >= content.size_s() - lhei) offset = content.size_s() - lhei; + return true; + case PIKbdListener::Home: + cur = offset = 0; + return true; + case PIKbdListener::End: + cur = content.size_s() - 1; + offset = content.size_s() - lhei; + return true; + case PIKbdListener::Return: + if (cur >= 0 && cur < content.size_s()) + raiseEvent(TileEvent(RowPressed, cur)); + return true; + case '*': + if (selection_mode == TileList::MultiSelection) { + PISet nsel; + for (int i = 0; i < content.size_s(); ++i) + if (!selected[i]) nsel << i; + selected = nsel; + } + raiseEvent(TileEvent(SelectionChanged)); + return true; + } + return PIScreenTile::keyEvent(key); +} + + + + +TileButtons::TileButtons(const PIString & n): PIScreenTile(n) { + focus_flags = CanHasFocus | NextByTab; + direction = Horizontal; + cur = 0; +} + + +void TileButtons::sizeHint(int & w, int & h) const { + w = h = 0; + if (direction == Horizontal) { + piForeachC (Button & b, content) + w += b.first.size_s() + 2; + w += piMaxi(0, content.size_s() - 1) * 2; + h += 3; + } else { + piForeachC (Button & b, content) + w = piMaxi(w, b.first.size_s() + 2 + 4); + h += content.size_s() * 3; + h += piMaxi(0, content.size_s() - 1); + } +} + + +void TileButtons::drawEvent(PIScreenDrawer * d) { + int cx = x, cy = y; + for (int i = 0; i < content.size_s(); ++i) { + Color cb = Cyan; + Color ct = Black; + int ff = 0; + if (i == cur && has_focus) { + cb = Blue; + ct = White; + ff = Bold; + } + Button & b(content[i]); + int cw = b.first.size_s() + 2, xo(0); + if (direction == Vertical) { + cw = width - 4; + xo = (cw - b.first.size_s()) / 2 - 1; + } + d->fillRect(cx, cy, cx + cw, cy + 3, ' ', Default, cb); + d->drawText(cx + 1 + xo, cy + 1, b.first, ct, Transparent, ff); + if (direction == Horizontal) + cx += b.first.size_s() + 4; + else + cy += 4; + } +} + + +bool TileButtons::keyEvent(PIKbdListener::KeyEvent key) { + switch (key.key) { + case PIKbdListener::LeftArrow: + case PIKbdListener::UpArrow: + cur--; + if (cur < 0) cur = 0; + return true; + case PIKbdListener::RightArrow: + case PIKbdListener::DownArrow: + cur++; + if (cur >= content.size_s()) cur = content.size_s() - 1; + return true; + case PIKbdListener::Space: + case PIKbdListener::Return: + raiseEvent(TileEvent(ButtonSelected, cur)); + return true; + }; + return PIScreenTile::keyEvent(key); +} diff --git a/src/console/piscreentiles.h b/src/console/piscreentiles.h new file mode 100644 index 00000000..4108aa24 --- /dev/null +++ b/src/console/piscreentiles.h @@ -0,0 +1,86 @@ +/*! \file piscreentiles.h + * \brief Various tiles for PIScreen +*/ +/* + PIP - Platform Independent Primitives + Various tiles for PIScreen + 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 . +*/ + +#ifndef PISCREENTILES_H +#define PISCREENTILES_H + +#include "piscreentile.h" + + +class TileSimple: public PIScreenTile { +public: + TileSimple(const PIString & n = PIString()); + typedef PIPair Row; + PIVector content; + PIScreenTypes::Alignment alignment; +protected: + void sizeHint(int & w, int & h) const; + void drawEvent(PIScreenDrawer * d); +}; + + + + +class TileList: public PIScreenTile { +public: + TileList(const PIString & n = PIString()); + enum SelectionMode { + NoSelection, + SingleSelection, + MultiSelection + }; + enum EventType { + SelectionChanged, + RowPressed + }; + typedef PIPair Row; + PIVector content; + PIScreenTypes::Alignment alignment; + SelectionMode selection_mode; +protected: + void sizeHint(int & w, int & h) const; + void drawEvent(PIScreenDrawer * d); + bool keyEvent(PIKbdListener::KeyEvent key); + int lhei, offset, cur; + PISet selected; +}; + + + + +class TileButtons: public PIScreenTile { +public: + TileButtons(const PIString & n = PIString()); + enum EventType { + ButtonSelected + }; + typedef PIPair Button; + PIVector