/* 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 | NextByArrowsHorizontal | 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 osp = piMini(3, lhei / 4); 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); //piCout << is << ie << offset << lhei << content.size_s(); 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; if (offset < 0) offset = 0; return true; case PIKbdListener::Home: cur = offset = 0; return true; case PIKbdListener::End: cur = content.size_s() - 1; offset = content.size_s() - lhei; if (offset < 0) offset = 0; 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); } TileButton::TileButton(const PIString & n): PIScreenTile(n) { focus_flags = CanHasFocus | NextByTab | NextByArrowsAll; } void TileButton::sizeHint(int & w, int & h) const { w = text.size_s() + 2; h = 1; } void TileButton::drawEvent(PIScreenDrawer * d) { Color cb = has_focus ? Blue : Cyan; Color ct = has_focus ? White : Black; int ff = has_focus ? Bold : 0; d->fillRect(x, y, x + width, y + 1, ' ', Default, cb); d->drawText(x, y, "[", ct, Transparent, ff); d->drawText(x + (width - text.size_s()) / 2, y, text, ct, Transparent, ff); d->drawText(x + width - 1, y, "]", ct, Transparent, ff); } bool TileButton::keyEvent(PIKbdListener::KeyEvent key) { if (key.key == PIKbdListener::Space || key.key == PIKbdListener::Return) { raiseEvent(TileEvent(ButtonClicked)); 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() + 4; w += piMaxi(0, content.size_s() - 1) * 2; h += 1; } else { piForeachC (Button & b, content) w = piMaxi(w, b.first.size_s() + 4); h += content.size_s(); 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 - 2; xo = (cw - b.first.size_s()) / 2 - 1; } d->fillRect(cx, cy, cx + cw + 2, cy + 1, ' ', Default, cb); d->drawText(cx, cy, "[", ct, Transparent, ff); d->drawText(cx + xo + 2, cy, b.first, ct, Transparent, ff); d->drawText(cx + cw + 1, cy, "]", ct, Transparent, ff); if (direction == Horizontal) cx += b.first.size_s() + 6; else cy += 2; } } 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); } TileCheck::TileCheck(const PIString & n): PIScreenTile(n) { focus_flags = CanHasFocus | NextByTab | NextByArrowsAll; toggled = false; } void TileCheck::sizeHint(int & w, int & h) const { w = text.size_s() + 4; h = 1; } void TileCheck::drawEvent(PIScreenDrawer * d) { Color cb = has_focus ? Blue : Cyan; Color ct = has_focus ? White : Black; int ff = has_focus ? Bold : 0; PIString cs("[ ]"); if (toggled) cs[1] = '*'; d->fillRect(x, y, x + width, y + 1, ' ', Default, cb); d->drawText(x, y, cs, ct, Transparent, ff); d->drawText(x + 4, y, text, ct, Transparent, ff); } bool TileCheck::keyEvent(PIKbdListener::KeyEvent key) { if (key.key == PIKbdListener::Space || key.key == PIKbdListener::Return) { toggled = !toggled; raiseEvent(TileEvent(Toggled, toggled)); return true; } return PIScreenTile::keyEvent(key); } TileProgress::TileProgress(const PIString & n): PIScreenTile(n) { maximum = 100.; value = 0.; suffix = " %"; } void TileProgress::sizeHint(int & w, int & h) const { w = 20; h = 1; } void TileProgress::drawEvent(PIScreenDrawer * d) { int v = maximum == 0. ? 0 : piClampd(piRoundd(value / maximum * 100.), 0, 100); PIString s = prefix + PIString::fromNumber(piRoundd(value)) + suffix; int w = piRoundd(v / 100. * width), sx = (width - s.size_s()) / 2; d->fillRect(x, y, x + width, y + 1, ' ', Default, Cyan); d->fillRect(x, y, x + w, y + 1, ' ', Default, Blue); if (w < sx) d->drawText(x + sx, y, s, Black, Transparent); else if (w >= sx + s.size_s()) d->drawText(x + sx, y, s, White, Transparent); else { int fw = w - sx; d->drawText(x + sx, y, s.left(fw), White, Transparent); d->drawText(x + sx + fw, y, s.cutLeft(fw), Black, Transparent); } }