/* PIP - Platform Independent Primitives Various tiles for PIScreen Ivan Pelipenko peri4ko@yandex.ru This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #include "piscreentiles.h" #include "piscreendrawer.h" using namespace PIScreenTypes; TileSimple::TileSimple(const PIString & n): PIScreenTile(n) { alignment = Left; } TileSimple::TileSimple(const TileSimple::Row & r): PIScreenTile() { alignment = Left; content << r; } void TileSimple::sizeHint(int & w, int & h) const { w = h = 0; for (const auto & 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); } } // TileScrollBar TileScrollBar::TileScrollBar(const PIString & n) { direction = Vertical; thickness = 1; minimum_ = value_ = 0; maximum_ = 100; } void TileScrollBar::setMinimum(int v) { minimum_ = v; _check(); } void TileScrollBar::setMaximum(int v) { maximum_ = v; _check(); } void TileScrollBar::setValue(int v) { value_ = v; _check(); } void TileScrollBar::_check() { value_ = piClampi(value_, minimum_, maximum_); } void TileScrollBar::sizeHint(int & w, int & h) const { w = h = 0; if (direction == Vertical) { w = thickness; h = 255; } else { h = thickness; w = 255; } } void TileScrollBar::drawEvent(PIScreenDrawer * d) { line_char = d->artChar(direction == Vertical ? PIScreenDrawer::LineVertical : PIScreenDrawer::LineHorizontal); d->fillRect(x_, y_, x_ + width_, y_ + height_, line_char, Green); if (value_ >= minimum_ && value_ <= maximum_) { if (direction == Vertical) { int c = piRoundf((float(value_ - minimum_) / (maximum_ - minimum_)) * (height_ - 1)); d->drawLine(x_, y_ + c, x_ + width_ - 1, y_ + c, ' ', Green, Green); } else { int c = piRoundf((float(value_ - minimum_) / (maximum_ - minimum_)) * (width_ - 1)); d->drawLine(x_ + c, y_, x_ + c, y_ + height_ - 1, ' ', Green, Green); } } } bool TileScrollBar::mouseEvent(PIKbdListener::MouseEvent me) { return true; } // TileList TileList::TileList(const PIString & n, SelectionMode sm): PIScreenTile(n) { alignment = Left; focus_flags = CanHasFocus | NextByArrowsHorizontal | NextByTab | FocusOnMouseOrWheel; lhei = offset = cur = 0; mouse_sel = false; selection_mode = sm; scroll = new TileScrollBar(); scroll->size_policy = Ignore; addTile(scroll); } void TileList::sizeHint(int & w, int & h) const { w = h = 0; for (const auto & r: content) w = piMaxi(w, r.first.size_s()); h = 3; } void TileList::resizeEvent(int w, int h) { scroll->x_ = x_ + width_ - 1; scroll->y_ = y_; scroll->width_ = 1; scroll->height_ = height_; } 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); if (ie < content.size_s()) d->drawText(x_, y_ + height_ - 1, 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); } scroll->setMaximum(piMaxi(0, content.size_s() - 1)); scroll->setValue(cur); } 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; case 'A': if (selection_mode == TileList::MultiSelection) { selected.clear(); for (int i = 0; i < content.size_s(); ++i) selected << i; } raiseEvent(TileEvent(SelectionChanged)); return true; } return PIScreenTile::keyEvent(key); } bool TileList::mouseEvent(PIKbdListener::MouseEvent me) { if (me.action == PIKbdListener::MouseButtonRelease) return true; int mp = me.y - y() - 1 + offset; if (mp < 0 || mp >= content.size_s()) return true; cur = mp; switch (me.action) { case PIKbdListener::MouseButtonPress: mouse_sel = !selected.contains(cur); break; case PIKbdListener::MouseButtonDblClick: keyEvent(PIKbdListener::KeyEvent(PIKbdListener::Return)); return true; default: break; } if (me.buttons[PIKbdListener::MouseRight]) { switch (selection_mode) { case SingleSelection: selected.clear(); selected << cur; raiseEvent(TileEvent(SelectionChanged)); break; case MultiSelection: if (mouse_sel) selected << cur; else selected.remove(cur); raiseEvent(TileEvent(SelectionChanged)); break; default: break; } } return true; } bool TileList::wheelEvent(PIKbdListener::WheelEvent we) { keyEvent(PIKbdListener::KeyEvent(we.direction ? PIKbdListener::PageUp : PIKbdListener::PageDown)); return true; } // TileButton TileButton::TileButton(const PIString & n): PIScreenTile(n) { focus_flags = CanHasFocus | NextByTab | NextByArrowsAll | FocusOnMouse; } 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); } bool TileButton::mouseEvent(PIKbdListener::MouseEvent me) { if (me.action != PIKbdListener::MouseButtonRelease) return true; keyEvent(PIKbdListener::KeyEvent(PIKbdListener::Return)); return true; } // TileButtons TileButtons::TileButtons(const PIString & n): PIScreenTile(n) { focus_flags = CanHasFocus | NextByTab | FocusOnMouse; direction = Horizontal; alignment = PIScreenTypes::Center; cur = 0; } void TileButtons::sizeHint(int & w, int & h) const { w = h = 0; if (direction == Horizontal) { for (const auto & b: content) w += b.first.size_s() + 4; w += piMaxi(0, content.size_s() - 1) * 2; h += 1; } else { for (const auto & 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_, shw, shh; sizeHint(shw, shh); btn_rects.resize(content.size()); int dx = 0; switch (alignment) { case PIScreenTypes::Center: dx = (width_ - shw) / 2; break; case PIScreenTypes::Right: dx = width_ - shw; break; default: break; } if (direction == PIScreenTypes::Horizontal) cx += dx; 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; } btn_rects[i] = Rect(cx, cy, cx + cw + 2, cy + 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); } bool TileButtons::mouseEvent(PIKbdListener::MouseEvent me) { if (me.action == PIKbdListener::MouseMove || me.action == PIKbdListener::MouseButtonPress) { for (int i = 0; i < btn_rects.size_s(); ++i) if (me.x >= btn_rects[i].x0 && me.x < btn_rects[i].x1 && me.y >= btn_rects[i].y0 && me.y < btn_rects[i].y1) { cur = i; break; } return true; } if (me.action != PIKbdListener::MouseButtonRelease) return true; keyEvent(PIKbdListener::KeyEvent(PIKbdListener::Return)); return true; } // TileCheck TileCheck::TileCheck(const PIString & n): PIScreenTile(n) { focus_flags = CanHasFocus | NextByTab | NextByArrowsAll | FocusOnMouse; 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); } bool TileCheck::mouseEvent(PIKbdListener::MouseEvent me) { if (me.action == PIKbdListener::MouseButtonPress) { keyEvent(PIKbdListener::KeyEvent(PIKbdListener::Return)); return true; } return false; } // TileProgress 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); } } // TilePICout TilePICout::TilePICout(const PIString & n): TileList(n) { max_lines = 1024; selection_mode = TileList::SingleSelection; PICout::setOutputDevices(PICout::Buffer); } void TilePICout::drawEvent(PIScreenDrawer * d) { PIString out = PICout::getBufferAndClear(); if (!out.isEmpty()) { PIStringList l = out.split("\n"); bool scroll = (cur == content.size_s() - 1) || !has_focus; for (const auto & s: l) content << TileList::Row(s.trimmed(), format); if (content.size_s() > max_lines) content.remove(0, content.size_s() - max_lines); if (scroll) { offset = piMaxi(0, content.size_s() - lhei); cur = content.size_s() - 1; } } TileList::drawEvent(d); } bool TilePICout::keyEvent(PIKbdListener::KeyEvent key) { if (key.key == 'C') { content.clear(); cur = offset = 0; return true; } return TileList::keyEvent(key); } // TileInput TileInput::TileInput(const PIString & n): PIScreenTile(n) { focus_flags = CanHasFocus | NextByTab | FocusOnMouse; back_format.color_back = White; format.color_char = Black; format.color_back = White; max_length = 1024; cur = offset = 0; inv = false; } void TileInput::sizeHint(int & w, int & h) const { w = 32; h = 1; } void TileInput::drawEvent(PIScreenDrawer * d) { PIString ps = text.mid(offset, width_ - 2); d->drawText(x_ + 1, y_, ps, (Color)format.color_char, Transparent, (CharFlags)format.flags); if (offset > 0) d->drawText(x_, y_, "<", Green, Black, Bold); if (text.size_s() - offset >= width_ - 2) d->drawText(x_ + width_ - 1, y_, ">", Green, Black, Bold); if (!has_focus) return; Color cb = (Color)format.color_char, cc = (Color)format.color_back; if (tm_blink.elapsed_m() >= 650) { tm_blink.reset(); inv = !inv; } if (inv) piSwap(cb, cc); d->drawText(x_ + 1 + cur - offset, y_, text.mid(cur, 1).expandLeftTo(1, ' '), cc, cb, (CharFlags)format.flags); } bool TileInput::keyEvent(PIKbdListener::KeyEvent key) { int lwid = piMaxi(0, width_ - 2); int oo(0), osp = piMini(3, lwid / 4); lwid--; switch (key.key) { case PIKbdListener::LeftArrow: 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; reserCursor(); return true; case PIKbdListener::RightArrow: cur++; oo++; if (key.modifiers[PIKbdListener::Ctrl]) { cur += 4; oo += 4; } if (cur > text.size_s()) cur = text.size_s(); if (cur - offset >= lwid - osp) offset += oo; if (offset >= text.size_s() - lwid) offset = text.size_s() - lwid; if (offset < 0) offset = 0; reserCursor(); return true; case PIKbdListener::Home: cur = offset = 0; reserCursor(); return true; case PIKbdListener::End: cur = text.size_s(); offset = text.size_s() - lwid; if (offset < 0) offset = 0; reserCursor(); return true; case PIKbdListener::Backspace: if (cur > text.size_s() || text.isEmpty()) return true; text.remove(cur - 1, 1); cur--; if (cur > text.size_s()) cur = text.size_s(); if (cur - offset >= lwid - osp) offset += oo; if (offset >= text.size_s() - lwid) offset = text.size_s() - lwid; if (offset < 0) offset = 0; reserCursor(); return true; case PIKbdListener::Delete: if (cur >= text.size_s() || text.isEmpty()) return true; text.remove(cur, 1); if (cur < 0) cur = 0; if (cur > text.size_s()) cur = text.size_s(); if (cur - offset < osp) offset += oo; if (offset < 0) offset = 0; reserCursor(); return true; case PIKbdListener::UpArrow: case PIKbdListener::DownArrow: case PIKbdListener::PageUp: case PIKbdListener::PageDown: case PIKbdListener::Insert: case PIKbdListener::Return: case PIKbdListener::Esc: case PIKbdListener::F1: case PIKbdListener::F2: case PIKbdListener::F3: case PIKbdListener::F4: case PIKbdListener::F5: case PIKbdListener::F6: case PIKbdListener::F7: case PIKbdListener::F8: case PIKbdListener::F9: case PIKbdListener::F10: case PIKbdListener::F11: case PIKbdListener::F12: break; default: text.insert(cur, PIChar((ushort)key.key)); cur++; oo++; if (cur - offset >= lwid - osp) offset += oo; if (offset >= text.size_s() - lwid) offset = text.size_s() - lwid; if (offset < 0) offset = 0; reserCursor(); return true; } return PIScreenTile::keyEvent(key); } void TileInput::reserCursor() { tm_blink.reset(); inv = false; }