aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py3/twisted/conch/insults/window.py
diff options
context:
space:
mode:
authorshmel1k <shmel1k@ydb.tech>2023-11-26 18:16:14 +0300
committershmel1k <shmel1k@ydb.tech>2023-11-26 18:43:30 +0300
commitb8cf9e88f4c5c64d9406af533d8948deb050d695 (patch)
tree218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py3/twisted/conch/insults/window.py
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
downloadydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py3/twisted/conch/insults/window.py')
-rw-r--r--contrib/python/Twisted/py3/twisted/conch/insults/window.py928
1 files changed, 928 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py3/twisted/conch/insults/window.py b/contrib/python/Twisted/py3/twisted/conch/insults/window.py
new file mode 100644
index 0000000000..c93fae7b21
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/conch/insults/window.py
@@ -0,0 +1,928 @@
+# -*- test-case-name: twisted.conch.test.test_window -*-
+
+"""
+Simple insults-based widget library
+
+@author: Jp Calderone
+"""
+
+import array
+
+from twisted.conch.insults import helper, insults
+from twisted.python import text as tptext
+
+
+class YieldFocus(Exception):
+ """
+ Input focus manipulation exception
+ """
+
+
+class BoundedTerminalWrapper:
+ def __init__(self, terminal, width, height, xoff, yoff):
+ self.width = width
+ self.height = height
+ self.xoff = xoff
+ self.yoff = yoff
+ self.terminal = terminal
+ self.cursorForward = terminal.cursorForward
+ self.selectCharacterSet = terminal.selectCharacterSet
+ self.selectGraphicRendition = terminal.selectGraphicRendition
+ self.saveCursor = terminal.saveCursor
+ self.restoreCursor = terminal.restoreCursor
+
+ def cursorPosition(self, x, y):
+ return self.terminal.cursorPosition(
+ self.xoff + min(self.width, x), self.yoff + min(self.height, y)
+ )
+
+ def cursorHome(self):
+ return self.terminal.cursorPosition(self.xoff, self.yoff)
+
+ def write(self, data):
+ return self.terminal.write(data)
+
+
+class Widget:
+ focused = False
+ parent = None
+ dirty = False
+ width = height = None
+
+ def repaint(self):
+ if not self.dirty:
+ self.dirty = True
+ if self.parent is not None and not self.parent.dirty:
+ self.parent.repaint()
+
+ def filthy(self):
+ self.dirty = True
+
+ def redraw(self, width, height, terminal):
+ self.filthy()
+ self.draw(width, height, terminal)
+
+ def draw(self, width, height, terminal):
+ if width != self.width or height != self.height or self.dirty:
+ self.width = width
+ self.height = height
+ self.dirty = False
+ self.render(width, height, terminal)
+
+ def render(self, width, height, terminal):
+ pass
+
+ def sizeHint(self):
+ return None
+
+ def keystrokeReceived(self, keyID, modifier):
+ if keyID == b"\t":
+ self.tabReceived(modifier)
+ elif keyID == b"\x7f":
+ self.backspaceReceived()
+ elif keyID in insults.FUNCTION_KEYS:
+ self.functionKeyReceived(keyID, modifier)
+ else:
+ self.characterReceived(keyID, modifier)
+
+ def tabReceived(self, modifier):
+ # XXX TODO - Handle shift+tab
+ raise YieldFocus()
+
+ def focusReceived(self):
+ """
+ Called when focus is being given to this widget.
+
+ May raise YieldFocus is this widget does not want focus.
+ """
+ self.focused = True
+ self.repaint()
+
+ def focusLost(self):
+ self.focused = False
+ self.repaint()
+
+ def backspaceReceived(self):
+ pass
+
+ def functionKeyReceived(self, keyID, modifier):
+ name = keyID
+ if not isinstance(keyID, str):
+ name = name.decode("utf-8")
+ func = getattr(self, "func_" + name, None)
+ if func is not None:
+ func(modifier)
+
+ def characterReceived(self, keyID, modifier):
+ pass
+
+
+class ContainerWidget(Widget):
+ """
+ @ivar focusedChild: The contained widget which currently has
+ focus, or None.
+ """
+
+ focusedChild = None
+ focused = False
+
+ def __init__(self):
+ Widget.__init__(self)
+ self.children = []
+
+ def addChild(self, child):
+ assert child.parent is None
+ child.parent = self
+ self.children.append(child)
+ if self.focusedChild is None and self.focused:
+ try:
+ child.focusReceived()
+ except YieldFocus:
+ pass
+ else:
+ self.focusedChild = child
+ self.repaint()
+
+ def remChild(self, child):
+ assert child.parent is self
+ child.parent = None
+ self.children.remove(child)
+ self.repaint()
+
+ def filthy(self):
+ for ch in self.children:
+ ch.filthy()
+ Widget.filthy(self)
+
+ def render(self, width, height, terminal):
+ for ch in self.children:
+ ch.draw(width, height, terminal)
+
+ def changeFocus(self):
+ self.repaint()
+
+ if self.focusedChild is not None:
+ self.focusedChild.focusLost()
+ focusedChild = self.focusedChild
+ self.focusedChild = None
+ try:
+ curFocus = self.children.index(focusedChild) + 1
+ except ValueError:
+ raise YieldFocus()
+ else:
+ curFocus = 0
+ while curFocus < len(self.children):
+ try:
+ self.children[curFocus].focusReceived()
+ except YieldFocus:
+ curFocus += 1
+ else:
+ self.focusedChild = self.children[curFocus]
+ return
+ # None of our children wanted focus
+ raise YieldFocus()
+
+ def focusReceived(self):
+ self.changeFocus()
+ self.focused = True
+
+ def keystrokeReceived(self, keyID, modifier):
+ if self.focusedChild is not None:
+ try:
+ self.focusedChild.keystrokeReceived(keyID, modifier)
+ except YieldFocus:
+ self.changeFocus()
+ self.repaint()
+ else:
+ Widget.keystrokeReceived(self, keyID, modifier)
+
+
+class TopWindow(ContainerWidget):
+ """
+ A top-level container object which provides focus wrap-around and paint
+ scheduling.
+
+ @ivar painter: A no-argument callable which will be invoked when this
+ widget needs to be redrawn.
+
+ @ivar scheduler: A one-argument callable which will be invoked with a
+ no-argument callable and should arrange for it to invoked at some point in
+ the near future. The no-argument callable will cause this widget and all
+ its children to be redrawn. It is typically beneficial for the no-argument
+ callable to be invoked at the end of handling for whatever event is
+ currently active; for example, it might make sense to call it at the end of
+ L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
+ Note, however, that since calls to this may also be made in response to no
+ apparent event, arrangements should be made for the function to be called
+ even if an event handler such as C{keystrokeReceived} is not on the call
+ stack (eg, using
+ L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
+ with a short timeout).
+ """
+
+ focused = True
+
+ def __init__(self, painter, scheduler):
+ ContainerWidget.__init__(self)
+ self.painter = painter
+ self.scheduler = scheduler
+
+ _paintCall = None
+
+ def repaint(self):
+ if self._paintCall is None:
+ self._paintCall = object()
+ self.scheduler(self._paint)
+ ContainerWidget.repaint(self)
+
+ def _paint(self):
+ self._paintCall = None
+ self.painter()
+
+ def changeFocus(self):
+ try:
+ ContainerWidget.changeFocus(self)
+ except YieldFocus:
+ try:
+ ContainerWidget.changeFocus(self)
+ except YieldFocus:
+ pass
+
+ def keystrokeReceived(self, keyID, modifier):
+ try:
+ ContainerWidget.keystrokeReceived(self, keyID, modifier)
+ except YieldFocus:
+ self.changeFocus()
+
+
+class AbsoluteBox(ContainerWidget):
+ def moveChild(self, child, x, y):
+ for n in range(len(self.children)):
+ if self.children[n][0] is child:
+ self.children[n] = (child, x, y)
+ break
+ else:
+ raise ValueError("No such child", child)
+
+ def render(self, width, height, terminal):
+ for ch, x, y in self.children:
+ wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
+ ch.draw(width, height, wrap)
+
+
+class _Box(ContainerWidget):
+ TOP, CENTER, BOTTOM = range(3)
+
+ def __init__(self, gravity=CENTER):
+ ContainerWidget.__init__(self)
+ self.gravity = gravity
+
+ def sizeHint(self):
+ height = 0
+ width = 0
+ for ch in self.children:
+ hint = ch.sizeHint()
+ if hint is None:
+ hint = (None, None)
+
+ if self.variableDimension == 0:
+ if hint[0] is None:
+ width = None
+ elif width is not None:
+ width += hint[0]
+ if hint[1] is None:
+ height = None
+ elif height is not None:
+ height = max(height, hint[1])
+ else:
+ if hint[0] is None:
+ width = None
+ elif width is not None:
+ width = max(width, hint[0])
+ if hint[1] is None:
+ height = None
+ elif height is not None:
+ height += hint[1]
+
+ return width, height
+
+ def render(self, width, height, terminal):
+ if not self.children:
+ return
+
+ greedy = 0
+ wants = []
+ for ch in self.children:
+ hint = ch.sizeHint()
+ if hint is None:
+ hint = (None, None)
+ if hint[self.variableDimension] is None:
+ greedy += 1
+ wants.append(hint[self.variableDimension])
+
+ length = (width, height)[self.variableDimension]
+ totalWant = sum(w for w in wants if w is not None)
+ if greedy:
+ leftForGreedy = int((length - totalWant) / greedy)
+
+ widthOffset = heightOffset = 0
+
+ for want, ch in zip(wants, self.children):
+ if want is None:
+ want = leftForGreedy
+
+ subWidth, subHeight = width, height
+ if self.variableDimension == 0:
+ subWidth = want
+ else:
+ subHeight = want
+
+ wrap = BoundedTerminalWrapper(
+ terminal,
+ subWidth,
+ subHeight,
+ widthOffset,
+ heightOffset,
+ )
+ ch.draw(subWidth, subHeight, wrap)
+ if self.variableDimension == 0:
+ widthOffset += want
+ else:
+ heightOffset += want
+
+
+class HBox(_Box):
+ variableDimension = 0
+
+
+class VBox(_Box):
+ variableDimension = 1
+
+
+class Packer(ContainerWidget):
+ def render(self, width, height, terminal):
+ if not self.children:
+ return
+
+ root = int(len(self.children) ** 0.5 + 0.5)
+ boxes = [VBox() for n in range(root)]
+ for n, ch in enumerate(self.children):
+ boxes[n % len(boxes)].addChild(ch)
+ h = HBox()
+ map(h.addChild, boxes)
+ h.render(width, height, terminal)
+
+
+class Canvas(Widget):
+ focused = False
+
+ contents = None
+
+ def __init__(self):
+ Widget.__init__(self)
+ self.resize(1, 1)
+
+ def resize(self, width, height):
+ contents = array.array("B", b" " * width * height)
+ if self.contents is not None:
+ for x in range(min(width, self._width)):
+ for y in range(min(height, self._height)):
+ contents[width * y + x] = self[x, y]
+ self.contents = contents
+ self._width = width
+ self._height = height
+ if self.x >= width:
+ self.x = width - 1
+ if self.y >= height:
+ self.y = height - 1
+
+ def __getitem__(self, index):
+ (x, y) = index
+ return self.contents[(self._width * y) + x]
+
+ def __setitem__(self, index, value):
+ (x, y) = index
+ self.contents[(self._width * y) + x] = value
+
+ def clear(self):
+ self.contents = array.array("B", b" " * len(self.contents))
+
+ def render(self, width, height, terminal):
+ if not width or not height:
+ return
+
+ if width != self._width or height != self._height:
+ self.resize(width, height)
+ for i in range(height):
+ terminal.cursorPosition(0, i)
+ text = self.contents[
+ self._width * i : self._width * i + self._width
+ ].tobytes()
+ text = text[:width]
+ terminal.write(text)
+
+
+def horizontalLine(terminal, y, left, right):
+ terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
+ terminal.cursorPosition(left, y)
+ terminal.write(b"\161" * (right - left))
+ terminal.selectCharacterSet(insults.CS_US, insults.G0)
+
+
+def verticalLine(terminal, x, top, bottom):
+ terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
+ for n in range(top, bottom):
+ terminal.cursorPosition(x, n)
+ terminal.write(b"\170")
+ terminal.selectCharacterSet(insults.CS_US, insults.G0)
+
+
+def rectangle(terminal, position, dimension):
+ """
+ Draw a rectangle
+
+ @type position: L{tuple}
+ @param position: A tuple of the (top, left) coordinates of the rectangle.
+ @type dimension: L{tuple}
+ @param dimension: A tuple of the (width, height) size of the rectangle.
+ """
+ (top, left) = position
+ (width, height) = dimension
+ terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
+
+ terminal.cursorPosition(top, left)
+ terminal.write(b"\154")
+ terminal.write(b"\161" * (width - 2))
+ terminal.write(b"\153")
+ for n in range(height - 2):
+ terminal.cursorPosition(left, top + n + 1)
+ terminal.write(b"\170")
+ terminal.cursorForward(width - 2)
+ terminal.write(b"\170")
+ terminal.cursorPosition(0, top + height - 1)
+ terminal.write(b"\155")
+ terminal.write(b"\161" * (width - 2))
+ terminal.write(b"\152")
+
+ terminal.selectCharacterSet(insults.CS_US, insults.G0)
+
+
+class Border(Widget):
+ def __init__(self, containee):
+ Widget.__init__(self)
+ self.containee = containee
+ self.containee.parent = self
+
+ def focusReceived(self):
+ return self.containee.focusReceived()
+
+ def focusLost(self):
+ return self.containee.focusLost()
+
+ def keystrokeReceived(self, keyID, modifier):
+ return self.containee.keystrokeReceived(keyID, modifier)
+
+ def sizeHint(self):
+ hint = self.containee.sizeHint()
+ if hint is None:
+ hint = (None, None)
+ if hint[0] is None:
+ x = None
+ else:
+ x = hint[0] + 2
+ if hint[1] is None:
+ y = None
+ else:
+ y = hint[1] + 2
+ return x, y
+
+ def filthy(self):
+ self.containee.filthy()
+ Widget.filthy(self)
+
+ def render(self, width, height, terminal):
+ if self.containee.focused:
+ terminal.write(b"\x1b[31m")
+ rectangle(terminal, (0, 0), (width, height))
+ terminal.write(b"\x1b[0m")
+ wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
+ self.containee.draw(width - 2, height - 2, wrap)
+
+
+class Button(Widget):
+ def __init__(self, label, onPress):
+ Widget.__init__(self)
+ self.label = label
+ self.onPress = onPress
+
+ def sizeHint(self):
+ return len(self.label), 1
+
+ def characterReceived(self, keyID, modifier):
+ if keyID == b"\r":
+ self.onPress()
+
+ def render(self, width, height, terminal):
+ terminal.cursorPosition(0, 0)
+ if self.focused:
+ terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
+ else:
+ terminal.write(self.label)
+
+
+class TextInput(Widget):
+ def __init__(self, maxwidth, onSubmit):
+ Widget.__init__(self)
+ self.onSubmit = onSubmit
+ self.maxwidth = maxwidth
+ self.buffer = b""
+ self.cursor = 0
+
+ def setText(self, text):
+ self.buffer = text[: self.maxwidth]
+ self.cursor = len(self.buffer)
+ self.repaint()
+
+ def func_LEFT_ARROW(self, modifier):
+ if self.cursor > 0:
+ self.cursor -= 1
+ self.repaint()
+
+ def func_RIGHT_ARROW(self, modifier):
+ if self.cursor < len(self.buffer):
+ self.cursor += 1
+ self.repaint()
+
+ def backspaceReceived(self):
+ if self.cursor > 0:
+ self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
+ self.cursor -= 1
+ self.repaint()
+
+ def characterReceived(self, keyID, modifier):
+ if keyID == b"\r":
+ self.onSubmit(self.buffer)
+ else:
+ if len(self.buffer) < self.maxwidth:
+ self.buffer = (
+ self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
+ )
+ self.cursor += 1
+ self.repaint()
+
+ def sizeHint(self):
+ return self.maxwidth + 1, 1
+
+ def render(self, width, height, terminal):
+ currentText = self._renderText()
+ terminal.cursorPosition(0, 0)
+ if self.focused:
+ terminal.write(currentText[: self.cursor])
+ cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
+ terminal.write(currentText[self.cursor + 1 :])
+ terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
+ else:
+ more = self.maxwidth - len(currentText)
+ terminal.write(currentText + b"_" * more)
+
+ def _renderText(self):
+ return self.buffer
+
+
+class PasswordInput(TextInput):
+ def _renderText(self):
+ return "*" * len(self.buffer)
+
+
+class TextOutput(Widget):
+ text = b""
+
+ def __init__(self, size=None):
+ Widget.__init__(self)
+ self.size = size
+
+ def sizeHint(self):
+ return self.size
+
+ def render(self, width, height, terminal):
+ terminal.cursorPosition(0, 0)
+ text = self.text[:width]
+ terminal.write(text + b" " * (width - len(text)))
+
+ def setText(self, text):
+ self.text = text
+ self.repaint()
+
+ def focusReceived(self):
+ raise YieldFocus()
+
+
+class TextOutputArea(TextOutput):
+ WRAP, TRUNCATE = range(2)
+
+ def __init__(self, size=None, longLines=WRAP):
+ TextOutput.__init__(self, size)
+ self.longLines = longLines
+
+ def render(self, width, height, terminal):
+ n = 0
+ inputLines = self.text.splitlines()
+ outputLines = []
+ while inputLines:
+ if self.longLines == self.WRAP:
+ line = inputLines.pop(0)
+ if not isinstance(line, str):
+ line = line.decode("utf-8")
+ wrappedLines = []
+ for wrappedLine in tptext.greedyWrap(line, width):
+ if not isinstance(wrappedLine, bytes):
+ wrappedLine = wrappedLine.encode("utf-8")
+ wrappedLines.append(wrappedLine)
+ outputLines.extend(wrappedLines or [b""])
+ else:
+ outputLines.append(inputLines.pop(0)[:width])
+ if len(outputLines) >= height:
+ break
+ for n, L in enumerate(outputLines[:height]):
+ terminal.cursorPosition(0, n)
+ terminal.write(L)
+
+
+class Viewport(Widget):
+ _xOffset = 0
+ _yOffset = 0
+
+ @property
+ def xOffset(self):
+ return self._xOffset
+
+ @xOffset.setter
+ def xOffset(self, value):
+ if self._xOffset != value:
+ self._xOffset = value
+ self.repaint()
+
+ @property
+ def yOffset(self):
+ return self._yOffset
+
+ @yOffset.setter
+ def yOffset(self, value):
+ if self._yOffset != value:
+ self._yOffset = value
+ self.repaint()
+
+ _width = 160
+ _height = 24
+
+ def __init__(self, containee):
+ Widget.__init__(self)
+ self.containee = containee
+ self.containee.parent = self
+
+ self._buf = helper.TerminalBuffer()
+ self._buf.width = self._width
+ self._buf.height = self._height
+ self._buf.connectionMade()
+
+ def filthy(self):
+ self.containee.filthy()
+ Widget.filthy(self)
+
+ def render(self, width, height, terminal):
+ self.containee.draw(self._width, self._height, self._buf)
+
+ # XXX /Lame/
+ for y, line in enumerate(
+ self._buf.lines[self._yOffset : self._yOffset + height]
+ ):
+ terminal.cursorPosition(0, y)
+ n = 0
+ for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
+ if ch is self._buf.void:
+ ch = b" "
+ terminal.write(ch)
+ if n < width:
+ terminal.write(b" " * (width - n - 1))
+
+
+class _Scrollbar(Widget):
+ def __init__(self, onScroll):
+ Widget.__init__(self)
+ self.onScroll = onScroll
+ self.percent = 0.0
+
+ def smaller(self):
+ self.percent = min(1.0, max(0.0, self.onScroll(-1)))
+ self.repaint()
+
+ def bigger(self):
+ self.percent = min(1.0, max(0.0, self.onScroll(+1)))
+ self.repaint()
+
+
+class HorizontalScrollbar(_Scrollbar):
+ def sizeHint(self):
+ return (None, 1)
+
+ def func_LEFT_ARROW(self, modifier):
+ self.smaller()
+
+ def func_RIGHT_ARROW(self, modifier):
+ self.bigger()
+
+ _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
+ _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
+ _bar = "\N{LIGHT SHADE}"
+ _slider = "\N{DARK SHADE}"
+
+ def render(self, width, height, terminal):
+ terminal.cursorPosition(0, 0)
+ n = width - 3
+ before = int(n * self.percent)
+ after = n - before
+ me = (
+ self._left
+ + (self._bar * before)
+ + self._slider
+ + (self._bar * after)
+ + self._right
+ )
+ terminal.write(me.encode("utf-8"))
+
+
+class VerticalScrollbar(_Scrollbar):
+ def sizeHint(self):
+ return (1, None)
+
+ def func_UP_ARROW(self, modifier):
+ self.smaller()
+
+ def func_DOWN_ARROW(self, modifier):
+ self.bigger()
+
+ _up = "\N{BLACK UP-POINTING TRIANGLE}"
+ _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
+ _bar = "\N{LIGHT SHADE}"
+ _slider = "\N{DARK SHADE}"
+
+ def render(self, width, height, terminal):
+ terminal.cursorPosition(0, 0)
+ knob = int(self.percent * (height - 2))
+ terminal.write(self._up.encode("utf-8"))
+ for i in range(1, height - 1):
+ terminal.cursorPosition(0, i)
+ if i != (knob + 1):
+ terminal.write(self._bar.encode("utf-8"))
+ else:
+ terminal.write(self._slider.encode("utf-8"))
+ terminal.cursorPosition(0, height - 1)
+ terminal.write(self._down.encode("utf-8"))
+
+
+class ScrolledArea(Widget):
+ """
+ A L{ScrolledArea} contains another widget wrapped in a viewport and
+ vertical and horizontal scrollbars for moving the viewport around.
+ """
+
+ def __init__(self, containee):
+ Widget.__init__(self)
+ self._viewport = Viewport(containee)
+ self._horiz = HorizontalScrollbar(self._horizScroll)
+ self._vert = VerticalScrollbar(self._vertScroll)
+
+ for w in self._viewport, self._horiz, self._vert:
+ w.parent = self
+
+ def _horizScroll(self, n):
+ self._viewport.xOffset += n
+ self._viewport.xOffset = max(0, self._viewport.xOffset)
+ return self._viewport.xOffset / 25.0
+
+ def _vertScroll(self, n):
+ self._viewport.yOffset += n
+ self._viewport.yOffset = max(0, self._viewport.yOffset)
+ return self._viewport.yOffset / 25.0
+
+ def func_UP_ARROW(self, modifier):
+ self._vert.smaller()
+
+ def func_DOWN_ARROW(self, modifier):
+ self._vert.bigger()
+
+ def func_LEFT_ARROW(self, modifier):
+ self._horiz.smaller()
+
+ def func_RIGHT_ARROW(self, modifier):
+ self._horiz.bigger()
+
+ def filthy(self):
+ self._viewport.filthy()
+ self._horiz.filthy()
+ self._vert.filthy()
+ Widget.filthy(self)
+
+ def render(self, width, height, terminal):
+ wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
+ self._viewport.draw(width - 2, height - 2, wrapper)
+ if self.focused:
+ terminal.write(b"\x1b[31m")
+ horizontalLine(terminal, 0, 1, width - 1)
+ verticalLine(terminal, 0, 1, height - 1)
+ self._vert.draw(
+ 1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
+ )
+ self._horiz.draw(
+ width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
+ )
+ terminal.write(b"\x1b[0m")
+
+
+def cursor(terminal, ch):
+ terminal.saveCursor()
+ terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
+ terminal.write(ch)
+ terminal.restoreCursor()
+ terminal.cursorForward()
+
+
+class Selection(Widget):
+ # Index into the sequence
+ focusedIndex = 0
+
+ # Offset into the displayed subset of the sequence
+ renderOffset = 0
+
+ def __init__(self, sequence, onSelect, minVisible=None):
+ Widget.__init__(self)
+ self.sequence = sequence
+ self.onSelect = onSelect
+ self.minVisible = minVisible
+ if minVisible is not None:
+ self._width = max(map(len, self.sequence))
+
+ def sizeHint(self):
+ if self.minVisible is not None:
+ return self._width, self.minVisible
+
+ def func_UP_ARROW(self, modifier):
+ if self.focusedIndex > 0:
+ self.focusedIndex -= 1
+ if self.renderOffset > 0:
+ self.renderOffset -= 1
+ self.repaint()
+
+ def func_PGUP(self, modifier):
+ if self.renderOffset != 0:
+ self.focusedIndex -= self.renderOffset
+ self.renderOffset = 0
+ else:
+ self.focusedIndex = max(0, self.focusedIndex - self.height)
+ self.repaint()
+
+ def func_DOWN_ARROW(self, modifier):
+ if self.focusedIndex < len(self.sequence) - 1:
+ self.focusedIndex += 1
+ if self.renderOffset < self.height - 1:
+ self.renderOffset += 1
+ self.repaint()
+
+ def func_PGDN(self, modifier):
+ if self.renderOffset != self.height - 1:
+ change = self.height - self.renderOffset - 1
+ if change + self.focusedIndex >= len(self.sequence):
+ change = len(self.sequence) - self.focusedIndex - 1
+ self.focusedIndex += change
+ self.renderOffset = self.height - 1
+ else:
+ self.focusedIndex = min(
+ len(self.sequence) - 1, self.focusedIndex + self.height
+ )
+ self.repaint()
+
+ def characterReceived(self, keyID, modifier):
+ if keyID == b"\r":
+ self.onSelect(self.sequence[self.focusedIndex])
+
+ def render(self, width, height, terminal):
+ self.height = height
+ start = self.focusedIndex - self.renderOffset
+ if start > len(self.sequence) - height:
+ start = max(0, len(self.sequence) - height)
+
+ elements = self.sequence[start : start + height]
+
+ for n, ele in enumerate(elements):
+ terminal.cursorPosition(0, n)
+ if n == self.renderOffset:
+ terminal.saveCursor()
+ if self.focused:
+ modes = str(insults.REVERSE_VIDEO), str(insults.BOLD)
+ else:
+ modes = (str(insults.REVERSE_VIDEO),)
+ terminal.selectGraphicRendition(*modes)
+ text = ele[:width]
+ terminal.write(text + (b" " * (width - len(text))))
+ if n == self.renderOffset:
+ terminal.restoreCursor()