Open Media Library Platform
This commit is contained in:
commit
411ad5b16f
5849 changed files with 1778641 additions and 0 deletions
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Insults: a replacement for Curses/S-Lang.
|
||||
|
||||
Very basic at the moment."""
|
||||
|
||||
from twisted.python import deprecate, versions
|
||||
|
||||
deprecate.deprecatedModuleAttribute(
|
||||
versions.Version("Twisted", 10, 1, 0),
|
||||
"Please use twisted.conch.insults.helper instead.",
|
||||
__name__, "colors")
|
||||
|
||||
deprecate.deprecatedModuleAttribute(
|
||||
versions.Version("Twisted", 10, 1, 0),
|
||||
"Please use twisted.conch.insults.insults instead.",
|
||||
__name__, "client")
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
"""
|
||||
You don't really want to use this module. Try insults.py instead.
|
||||
"""
|
||||
|
||||
from twisted.internet import protocol
|
||||
|
||||
class InsultsClient(protocol.Protocol):
|
||||
|
||||
escapeTimeout = 0.2
|
||||
|
||||
def __init__(self):
|
||||
self.width = self.height = None
|
||||
self.xpos = self.ypos = 0
|
||||
self.commandQueue = []
|
||||
self.inEscape = ''
|
||||
|
||||
def setSize(self, width, height):
|
||||
call = 0
|
||||
if self.width:
|
||||
call = 1
|
||||
self.width = width
|
||||
self.height = height
|
||||
if call:
|
||||
self.windowSizeChanged()
|
||||
|
||||
def dataReceived(self, data):
|
||||
from twisted.internet import reactor
|
||||
for ch in data:
|
||||
if ch == '\x1b':
|
||||
if self.inEscape:
|
||||
self.keyReceived(ch)
|
||||
self.inEscape = ''
|
||||
else:
|
||||
self.inEscape = ch
|
||||
self.escapeCall = reactor.callLater(self.escapeTimeout,
|
||||
self.endEscape)
|
||||
elif ch in 'ABCD' and self.inEscape:
|
||||
self.inEscape = ''
|
||||
self.escapeCall.cancel()
|
||||
if ch == 'A':
|
||||
self.keyReceived('<Up>')
|
||||
elif ch == 'B':
|
||||
self.keyReceived('<Down>')
|
||||
elif ch == 'C':
|
||||
self.keyReceived('<Right>')
|
||||
elif ch == 'D':
|
||||
self.keyReceived('<Left>')
|
||||
elif self.inEscape:
|
||||
self.inEscape += ch
|
||||
else:
|
||||
self.keyReceived(ch)
|
||||
|
||||
def endEscape(self):
|
||||
ch = self.inEscape
|
||||
self.inEscape = ''
|
||||
self.keyReceived(ch)
|
||||
|
||||
def initScreen(self):
|
||||
self.transport.write('\x1b=\x1b[?1h')
|
||||
|
||||
def gotoXY(self, x, y):
|
||||
"""Go to a position on the screen.
|
||||
"""
|
||||
self.xpos = x
|
||||
self.ypos = y
|
||||
self.commandQueue.append(('gotoxy', x, y))
|
||||
|
||||
def writeCh(self, ch):
|
||||
"""Write a character to the screen. If we're at the end of the row,
|
||||
ignore the write.
|
||||
"""
|
||||
if self.xpos < self.width - 1:
|
||||
self.commandQueue.append(('write', ch))
|
||||
self.xpos += 1
|
||||
|
||||
def writeStr(self, s):
|
||||
"""Write a string to the screen. This does not wrap a the edge of the
|
||||
screen, and stops at \\r and \\n.
|
||||
"""
|
||||
s = s[:self.width-self.xpos]
|
||||
if '\n' in s:
|
||||
s=s[:s.find('\n')]
|
||||
if '\r' in s:
|
||||
s=s[:s.find('\r')]
|
||||
self.commandQueue.append(('write', s))
|
||||
self.xpos += len(s)
|
||||
|
||||
def eraseToLine(self):
|
||||
"""Erase from the current position to the end of the line.
|
||||
"""
|
||||
self.commandQueue.append(('eraseeol',))
|
||||
|
||||
def eraseToScreen(self):
|
||||
"""Erase from the current position to the end of the screen.
|
||||
"""
|
||||
self.commandQueue.append(('eraseeos',))
|
||||
|
||||
def clearScreen(self):
|
||||
"""Clear the screen, and return the cursor to 0, 0.
|
||||
"""
|
||||
self.commandQueue = [('cls',)]
|
||||
self.xpos = self.ypos = 0
|
||||
|
||||
def setAttributes(self, *attrs):
|
||||
"""Set the attributes for drawing on the screen.
|
||||
"""
|
||||
self.commandQueue.append(('attributes', attrs))
|
||||
|
||||
def refresh(self):
|
||||
"""Redraw the screen.
|
||||
"""
|
||||
redraw = ''
|
||||
for command in self.commandQueue:
|
||||
if command[0] == 'gotoxy':
|
||||
redraw += '\x1b[%i;%iH' % (command[2]+1, command[1]+1)
|
||||
elif command[0] == 'write':
|
||||
redraw += command[1]
|
||||
elif command[0] == 'eraseeol':
|
||||
redraw += '\x1b[0K'
|
||||
elif command[0] == 'eraseeos':
|
||||
redraw += '\x1b[OJ'
|
||||
elif command[0] == 'cls':
|
||||
redraw += '\x1b[H\x1b[J'
|
||||
elif command[0] == 'attributes':
|
||||
redraw += '\x1b[%sm' % ';'.join(map(str, command[1]))
|
||||
else:
|
||||
print command
|
||||
self.commandQueue = []
|
||||
self.transport.write(redraw)
|
||||
|
||||
def windowSizeChanged(self):
|
||||
"""Called when the size of the window changes.
|
||||
Might want to redraw the screen here, or something.
|
||||
"""
|
||||
|
||||
def keyReceived(self, key):
|
||||
"""Called when the user hits a key.
|
||||
"""
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
"""
|
||||
You don't really want to use this module. Try helper.py instead.
|
||||
"""
|
||||
|
||||
CLEAR = 0
|
||||
BOLD = 1
|
||||
DIM = 2
|
||||
ITALIC = 3
|
||||
UNDERSCORE = 4
|
||||
BLINK_SLOW = 5
|
||||
BLINK_FAST = 6
|
||||
REVERSE = 7
|
||||
CONCEALED = 8
|
||||
FG_BLACK = 30
|
||||
FG_RED = 31
|
||||
FG_GREEN = 32
|
||||
FG_YELLOW = 33
|
||||
FG_BLUE = 34
|
||||
FG_MAGENTA = 35
|
||||
FG_CYAN = 36
|
||||
FG_WHITE = 37
|
||||
BG_BLACK = 40
|
||||
BG_RED = 41
|
||||
BG_GREEN = 42
|
||||
BG_YELLOW = 43
|
||||
BG_BLUE = 44
|
||||
BG_MAGENTA = 45
|
||||
BG_CYAN = 46
|
||||
BG_WHITE = 47
|
||||
|
|
@ -0,0 +1,463 @@
|
|||
# -*- test-case-name: twisted.conch.test.test_helper -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Partial in-memory terminal emulator
|
||||
|
||||
@author: Jp Calderone
|
||||
"""
|
||||
|
||||
import re, string
|
||||
|
||||
from zope.interface import implements
|
||||
|
||||
from twisted.internet import defer, protocol, reactor
|
||||
from twisted.python import log, _textattributes
|
||||
from twisted.python.deprecate import deprecated, deprecatedModuleAttribute
|
||||
from twisted.python.versions import Version
|
||||
from twisted.conch.insults import insults
|
||||
|
||||
FOREGROUND = 30
|
||||
BACKGROUND = 40
|
||||
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, N_COLORS = range(9)
|
||||
|
||||
|
||||
|
||||
class _FormattingState(_textattributes._FormattingStateMixin):
|
||||
"""
|
||||
Represents the formatting state/attributes of a single character.
|
||||
|
||||
Character set, intensity, underlinedness, blinkitude, video
|
||||
reversal, as well as foreground and background colors made up a
|
||||
character's attributes.
|
||||
"""
|
||||
compareAttributes = (
|
||||
'charset', 'bold', 'underline', 'blink', 'reverseVideo', 'foreground',
|
||||
'background', '_subtracting')
|
||||
|
||||
|
||||
def __init__(self, charset=insults.G0, bold=False, underline=False,
|
||||
blink=False, reverseVideo=False, foreground=WHITE,
|
||||
background=BLACK, _subtracting=False):
|
||||
self.charset = charset
|
||||
self.bold = bold
|
||||
self.underline = underline
|
||||
self.blink = blink
|
||||
self.reverseVideo = reverseVideo
|
||||
self.foreground = foreground
|
||||
self.background = background
|
||||
self._subtracting = _subtracting
|
||||
|
||||
|
||||
@deprecated(Version('Twisted', 13, 1, 0))
|
||||
def wantOne(self, **kw):
|
||||
"""
|
||||
Add a character attribute to a copy of this formatting state.
|
||||
|
||||
@param **kw: An optional attribute name and value can be provided with
|
||||
a keyword argument.
|
||||
|
||||
@return: A formatting state instance with the new attribute.
|
||||
|
||||
@see: L{DefaultFormattingState._withAttribute}.
|
||||
"""
|
||||
k, v = kw.popitem()
|
||||
return self._withAttribute(k, v)
|
||||
|
||||
|
||||
def toVT102(self):
|
||||
# Spit out a vt102 control sequence that will set up
|
||||
# all the attributes set here. Except charset.
|
||||
attrs = []
|
||||
if self._subtracting:
|
||||
attrs.append(0)
|
||||
if self.bold:
|
||||
attrs.append(insults.BOLD)
|
||||
if self.underline:
|
||||
attrs.append(insults.UNDERLINE)
|
||||
if self.blink:
|
||||
attrs.append(insults.BLINK)
|
||||
if self.reverseVideo:
|
||||
attrs.append(insults.REVERSE_VIDEO)
|
||||
if self.foreground != WHITE:
|
||||
attrs.append(FOREGROUND + self.foreground)
|
||||
if self.background != BLACK:
|
||||
attrs.append(BACKGROUND + self.background)
|
||||
if attrs:
|
||||
return '\x1b[' + ';'.join(map(str, attrs)) + 'm'
|
||||
return ''
|
||||
|
||||
CharacterAttribute = _FormattingState
|
||||
|
||||
deprecatedModuleAttribute(
|
||||
Version('Twisted', 13, 1, 0),
|
||||
'Use twisted.conch.insults.text.assembleFormattedText instead.',
|
||||
'twisted.conch.insults.helper',
|
||||
'CharacterAttribute')
|
||||
|
||||
|
||||
|
||||
# XXX - need to support scroll regions and scroll history
|
||||
class TerminalBuffer(protocol.Protocol):
|
||||
"""
|
||||
An in-memory terminal emulator.
|
||||
"""
|
||||
implements(insults.ITerminalTransport)
|
||||
|
||||
for keyID in ('UP_ARROW', 'DOWN_ARROW', 'RIGHT_ARROW', 'LEFT_ARROW',
|
||||
'HOME', 'INSERT', 'DELETE', 'END', 'PGUP', 'PGDN',
|
||||
'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9',
|
||||
'F10', 'F11', 'F12'):
|
||||
exec '%s = object()' % (keyID,)
|
||||
|
||||
TAB = '\t'
|
||||
BACKSPACE = '\x7f'
|
||||
|
||||
width = 80
|
||||
height = 24
|
||||
|
||||
fill = ' '
|
||||
void = object()
|
||||
|
||||
def getCharacter(self, x, y):
|
||||
return self.lines[y][x]
|
||||
|
||||
def connectionMade(self):
|
||||
self.reset()
|
||||
|
||||
def write(self, bytes):
|
||||
"""
|
||||
Add the given printable bytes to the terminal.
|
||||
|
||||
Line feeds in C{bytes} will be replaced with carriage return / line
|
||||
feed pairs.
|
||||
"""
|
||||
for b in bytes.replace('\n', '\r\n'):
|
||||
self.insertAtCursor(b)
|
||||
|
||||
def _currentFormattingState(self):
|
||||
return _FormattingState(self.activeCharset, **self.graphicRendition)
|
||||
|
||||
def insertAtCursor(self, b):
|
||||
"""
|
||||
Add one byte to the terminal at the cursor and make consequent state
|
||||
updates.
|
||||
|
||||
If b is a carriage return, move the cursor to the beginning of the
|
||||
current row.
|
||||
|
||||
If b is a line feed, move the cursor to the next row or scroll down if
|
||||
the cursor is already in the last row.
|
||||
|
||||
Otherwise, if b is printable, put it at the cursor position (inserting
|
||||
or overwriting as dictated by the current mode) and move the cursor.
|
||||
"""
|
||||
if b == '\r':
|
||||
self.x = 0
|
||||
elif b == '\n':
|
||||
self._scrollDown()
|
||||
elif b in string.printable:
|
||||
if self.x >= self.width:
|
||||
self.nextLine()
|
||||
ch = (b, self._currentFormattingState())
|
||||
if self.modes.get(insults.modes.IRM):
|
||||
self.lines[self.y][self.x:self.x] = [ch]
|
||||
self.lines[self.y].pop()
|
||||
else:
|
||||
self.lines[self.y][self.x] = ch
|
||||
self.x += 1
|
||||
|
||||
def _emptyLine(self, width):
|
||||
return [(self.void, self._currentFormattingState())
|
||||
for i in xrange(width)]
|
||||
|
||||
def _scrollDown(self):
|
||||
self.y += 1
|
||||
if self.y >= self.height:
|
||||
self.y -= 1
|
||||
del self.lines[0]
|
||||
self.lines.append(self._emptyLine(self.width))
|
||||
|
||||
def _scrollUp(self):
|
||||
self.y -= 1
|
||||
if self.y < 0:
|
||||
self.y = 0
|
||||
del self.lines[-1]
|
||||
self.lines.insert(0, self._emptyLine(self.width))
|
||||
|
||||
def cursorUp(self, n=1):
|
||||
self.y = max(0, self.y - n)
|
||||
|
||||
def cursorDown(self, n=1):
|
||||
self.y = min(self.height - 1, self.y + n)
|
||||
|
||||
def cursorBackward(self, n=1):
|
||||
self.x = max(0, self.x - n)
|
||||
|
||||
def cursorForward(self, n=1):
|
||||
self.x = min(self.width, self.x + n)
|
||||
|
||||
def cursorPosition(self, column, line):
|
||||
self.x = column
|
||||
self.y = line
|
||||
|
||||
def cursorHome(self):
|
||||
self.x = self.home.x
|
||||
self.y = self.home.y
|
||||
|
||||
def index(self):
|
||||
self._scrollDown()
|
||||
|
||||
def reverseIndex(self):
|
||||
self._scrollUp()
|
||||
|
||||
def nextLine(self):
|
||||
"""
|
||||
Update the cursor position attributes and scroll down if appropriate.
|
||||
"""
|
||||
self.x = 0
|
||||
self._scrollDown()
|
||||
|
||||
def saveCursor(self):
|
||||
self._savedCursor = (self.x, self.y)
|
||||
|
||||
def restoreCursor(self):
|
||||
self.x, self.y = self._savedCursor
|
||||
del self._savedCursor
|
||||
|
||||
def setModes(self, modes):
|
||||
for m in modes:
|
||||
self.modes[m] = True
|
||||
|
||||
def resetModes(self, modes):
|
||||
for m in modes:
|
||||
try:
|
||||
del self.modes[m]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def setPrivateModes(self, modes):
|
||||
"""
|
||||
Enable the given modes.
|
||||
|
||||
Track which modes have been enabled so that the implementations of
|
||||
other L{insults.ITerminalTransport} methods can be properly implemented
|
||||
to respect these settings.
|
||||
|
||||
@see: L{resetPrivateModes}
|
||||
@see: L{insults.ITerminalTransport.setPrivateModes}
|
||||
"""
|
||||
for m in modes:
|
||||
self.privateModes[m] = True
|
||||
|
||||
|
||||
def resetPrivateModes(self, modes):
|
||||
"""
|
||||
Disable the given modes.
|
||||
|
||||
@see: L{setPrivateModes}
|
||||
@see: L{insults.ITerminalTransport.resetPrivateModes}
|
||||
"""
|
||||
for m in modes:
|
||||
try:
|
||||
del self.privateModes[m]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def applicationKeypadMode(self):
|
||||
self.keypadMode = 'app'
|
||||
|
||||
def numericKeypadMode(self):
|
||||
self.keypadMode = 'num'
|
||||
|
||||
def selectCharacterSet(self, charSet, which):
|
||||
self.charsets[which] = charSet
|
||||
|
||||
def shiftIn(self):
|
||||
self.activeCharset = insults.G0
|
||||
|
||||
def shiftOut(self):
|
||||
self.activeCharset = insults.G1
|
||||
|
||||
def singleShift2(self):
|
||||
oldActiveCharset = self.activeCharset
|
||||
self.activeCharset = insults.G2
|
||||
f = self.insertAtCursor
|
||||
def insertAtCursor(b):
|
||||
f(b)
|
||||
del self.insertAtCursor
|
||||
self.activeCharset = oldActiveCharset
|
||||
self.insertAtCursor = insertAtCursor
|
||||
|
||||
def singleShift3(self):
|
||||
oldActiveCharset = self.activeCharset
|
||||
self.activeCharset = insults.G3
|
||||
f = self.insertAtCursor
|
||||
def insertAtCursor(b):
|
||||
f(b)
|
||||
del self.insertAtCursor
|
||||
self.activeCharset = oldActiveCharset
|
||||
self.insertAtCursor = insertAtCursor
|
||||
|
||||
def selectGraphicRendition(self, *attributes):
|
||||
for a in attributes:
|
||||
if a == insults.NORMAL:
|
||||
self.graphicRendition = {
|
||||
'bold': False,
|
||||
'underline': False,
|
||||
'blink': False,
|
||||
'reverseVideo': False,
|
||||
'foreground': WHITE,
|
||||
'background': BLACK}
|
||||
elif a == insults.BOLD:
|
||||
self.graphicRendition['bold'] = True
|
||||
elif a == insults.UNDERLINE:
|
||||
self.graphicRendition['underline'] = True
|
||||
elif a == insults.BLINK:
|
||||
self.graphicRendition['blink'] = True
|
||||
elif a == insults.REVERSE_VIDEO:
|
||||
self.graphicRendition['reverseVideo'] = True
|
||||
else:
|
||||
try:
|
||||
v = int(a)
|
||||
except ValueError:
|
||||
log.msg("Unknown graphic rendition attribute: " + repr(a))
|
||||
else:
|
||||
if FOREGROUND <= v <= FOREGROUND + N_COLORS:
|
||||
self.graphicRendition['foreground'] = v - FOREGROUND
|
||||
elif BACKGROUND <= v <= BACKGROUND + N_COLORS:
|
||||
self.graphicRendition['background'] = v - BACKGROUND
|
||||
else:
|
||||
log.msg("Unknown graphic rendition attribute: " + repr(a))
|
||||
|
||||
def eraseLine(self):
|
||||
self.lines[self.y] = self._emptyLine(self.width)
|
||||
|
||||
def eraseToLineEnd(self):
|
||||
width = self.width - self.x
|
||||
self.lines[self.y][self.x:] = self._emptyLine(width)
|
||||
|
||||
def eraseToLineBeginning(self):
|
||||
self.lines[self.y][:self.x + 1] = self._emptyLine(self.x + 1)
|
||||
|
||||
def eraseDisplay(self):
|
||||
self.lines = [self._emptyLine(self.width) for i in xrange(self.height)]
|
||||
|
||||
def eraseToDisplayEnd(self):
|
||||
self.eraseToLineEnd()
|
||||
height = self.height - self.y - 1
|
||||
self.lines[self.y + 1:] = [self._emptyLine(self.width) for i in range(height)]
|
||||
|
||||
def eraseToDisplayBeginning(self):
|
||||
self.eraseToLineBeginning()
|
||||
self.lines[:self.y] = [self._emptyLine(self.width) for i in range(self.y)]
|
||||
|
||||
def deleteCharacter(self, n=1):
|
||||
del self.lines[self.y][self.x:self.x+n]
|
||||
self.lines[self.y].extend(self._emptyLine(min(self.width - self.x, n)))
|
||||
|
||||
def insertLine(self, n=1):
|
||||
self.lines[self.y:self.y] = [self._emptyLine(self.width) for i in range(n)]
|
||||
del self.lines[self.height:]
|
||||
|
||||
def deleteLine(self, n=1):
|
||||
del self.lines[self.y:self.y+n]
|
||||
self.lines.extend([self._emptyLine(self.width) for i in range(n)])
|
||||
|
||||
def reportCursorPosition(self):
|
||||
return (self.x, self.y)
|
||||
|
||||
def reset(self):
|
||||
self.home = insults.Vector(0, 0)
|
||||
self.x = self.y = 0
|
||||
self.modes = {}
|
||||
self.privateModes = {}
|
||||
self.setPrivateModes([insults.privateModes.AUTO_WRAP,
|
||||
insults.privateModes.CURSOR_MODE])
|
||||
self.numericKeypad = 'app'
|
||||
self.activeCharset = insults.G0
|
||||
self.graphicRendition = {
|
||||
'bold': False,
|
||||
'underline': False,
|
||||
'blink': False,
|
||||
'reverseVideo': False,
|
||||
'foreground': WHITE,
|
||||
'background': BLACK}
|
||||
self.charsets = {
|
||||
insults.G0: insults.CS_US,
|
||||
insults.G1: insults.CS_US,
|
||||
insults.G2: insults.CS_ALTERNATE,
|
||||
insults.G3: insults.CS_ALTERNATE_SPECIAL}
|
||||
self.eraseDisplay()
|
||||
|
||||
def unhandledControlSequence(self, buf):
|
||||
print 'Could not handle', repr(buf)
|
||||
|
||||
def __str__(self):
|
||||
lines = []
|
||||
for L in self.lines:
|
||||
buf = []
|
||||
length = 0
|
||||
for (ch, attr) in L:
|
||||
if ch is not self.void:
|
||||
buf.append(ch)
|
||||
length = len(buf)
|
||||
else:
|
||||
buf.append(self.fill)
|
||||
lines.append(''.join(buf[:length]))
|
||||
return '\n'.join(lines)
|
||||
|
||||
class ExpectationTimeout(Exception):
|
||||
pass
|
||||
|
||||
class ExpectableBuffer(TerminalBuffer):
|
||||
_mark = 0
|
||||
|
||||
def connectionMade(self):
|
||||
TerminalBuffer.connectionMade(self)
|
||||
self._expecting = []
|
||||
|
||||
def write(self, bytes):
|
||||
TerminalBuffer.write(self, bytes)
|
||||
self._checkExpected()
|
||||
|
||||
def cursorHome(self):
|
||||
TerminalBuffer.cursorHome(self)
|
||||
self._mark = 0
|
||||
|
||||
def _timeoutExpected(self, d):
|
||||
d.errback(ExpectationTimeout())
|
||||
self._checkExpected()
|
||||
|
||||
def _checkExpected(self):
|
||||
s = str(self)[self._mark:]
|
||||
while self._expecting:
|
||||
expr, timer, deferred = self._expecting[0]
|
||||
if timer and not timer.active():
|
||||
del self._expecting[0]
|
||||
continue
|
||||
for match in expr.finditer(s):
|
||||
if timer:
|
||||
timer.cancel()
|
||||
del self._expecting[0]
|
||||
self._mark += match.end()
|
||||
s = s[match.end():]
|
||||
deferred.callback(match)
|
||||
break
|
||||
else:
|
||||
return
|
||||
|
||||
def expect(self, expression, timeout=None, scheduler=reactor):
|
||||
d = defer.Deferred()
|
||||
timer = None
|
||||
if timeout:
|
||||
timer = scheduler.callLater(timeout, self._timeoutExpected, d)
|
||||
self._expecting.append((re.compile(expression), timer, d))
|
||||
self._checkExpected()
|
||||
return d
|
||||
|
||||
__all__ = [
|
||||
'CharacterAttribute', 'TerminalBuffer', 'ExpectableBuffer']
|
||||
1087
Linux/lib/python2.7/site-packages/twisted/conch/insults/insults.py
Normal file
1087
Linux/lib/python2.7/site-packages/twisted/conch/insults/insults.py
Normal file
File diff suppressed because it is too large
Load diff
175
Linux/lib/python2.7/site-packages/twisted/conch/insults/text.py
Normal file
175
Linux/lib/python2.7/site-packages/twisted/conch/insults/text.py
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
# -*- test-case-name: twisted.conch.test.test_text -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Character attribute manipulation API.
|
||||
|
||||
This module provides a domain-specific language (using Python syntax)
|
||||
for the creation of text with additional display attributes associated
|
||||
with it. It is intended as an alternative to manually building up
|
||||
strings containing ECMA 48 character attribute control codes. It
|
||||
currently supports foreground and background colors (black, red,
|
||||
green, yellow, blue, magenta, cyan, and white), intensity selection,
|
||||
underlining, blinking and reverse video. Character set selection
|
||||
support is planned.
|
||||
|
||||
Character attributes are specified by using two Python operations:
|
||||
attribute lookup and indexing. For example, the string \"Hello
|
||||
world\" with red foreground and all other attributes set to their
|
||||
defaults, assuming the name twisted.conch.insults.text.attributes has
|
||||
been imported and bound to the name \"A\" (with the statement C{from
|
||||
twisted.conch.insults.text import attributes as A}, for example) one
|
||||
uses this expression::
|
||||
|
||||
A.fg.red[\"Hello world\"]
|
||||
|
||||
Other foreground colors are set by substituting their name for
|
||||
\"red\". To set both a foreground and a background color, this
|
||||
expression is used::
|
||||
|
||||
A.fg.red[A.bg.green[\"Hello world\"]]
|
||||
|
||||
Note that either A.bg.green can be nested within A.fg.red or vice
|
||||
versa. Also note that multiple items can be nested within a single
|
||||
index operation by separating them with commas::
|
||||
|
||||
A.bg.green[A.fg.red[\"Hello\"], " ", A.fg.blue[\"world\"]]
|
||||
|
||||
Other character attributes are set in a similar fashion. To specify a
|
||||
blinking version of the previous expression::
|
||||
|
||||
A.blink[A.bg.green[A.fg.red[\"Hello\"], " ", A.fg.blue[\"world\"]]]
|
||||
|
||||
C{A.reverseVideo}, C{A.underline}, and C{A.bold} are also valid.
|
||||
|
||||
A third operation is actually supported: unary negation. This turns
|
||||
off an attribute when an enclosing expression would otherwise have
|
||||
caused it to be on. For example::
|
||||
|
||||
A.underline[A.fg.red[\"Hello\", -A.underline[\" world\"]]]
|
||||
|
||||
A formatting structure can then be serialized into a string containing the
|
||||
necessary VT102 control codes with L{assembleFormattedText}.
|
||||
|
||||
@see: L{twisted.conch.insults.text.attributes}
|
||||
@author: Jp Calderone
|
||||
"""
|
||||
|
||||
from twisted.conch.insults import helper, insults
|
||||
from twisted.python import _textattributes
|
||||
from twisted.python.deprecate import deprecatedModuleAttribute
|
||||
from twisted.python.versions import Version
|
||||
|
||||
|
||||
|
||||
flatten = _textattributes.flatten
|
||||
|
||||
deprecatedModuleAttribute(
|
||||
Version('Twisted', 13, 1, 0),
|
||||
'Use twisted.conch.insults.text.assembleFormattedText instead.',
|
||||
'twisted.conch.insults.text',
|
||||
'flatten')
|
||||
|
||||
_TEXT_COLORS = {
|
||||
'black': helper.BLACK,
|
||||
'red': helper.RED,
|
||||
'green': helper.GREEN,
|
||||
'yellow': helper.YELLOW,
|
||||
'blue': helper.BLUE,
|
||||
'magenta': helper.MAGENTA,
|
||||
'cyan': helper.CYAN,
|
||||
'white': helper.WHITE}
|
||||
|
||||
|
||||
|
||||
class _CharacterAttributes(_textattributes.CharacterAttributesMixin):
|
||||
"""
|
||||
Factory for character attributes, including foreground and background color
|
||||
and non-color attributes such as bold, reverse video and underline.
|
||||
|
||||
Character attributes are applied to actual text by using object
|
||||
indexing-syntax (C{obj['abc']}) after accessing a factory attribute, for
|
||||
example::
|
||||
|
||||
attributes.bold['Some text']
|
||||
|
||||
These can be nested to mix attributes::
|
||||
|
||||
attributes.bold[attributes.underline['Some text']]
|
||||
|
||||
And multiple values can be passed::
|
||||
|
||||
attributes.normal[attributes.bold['Some'], ' text']
|
||||
|
||||
Non-color attributes can be accessed by attribute name, available
|
||||
attributes are:
|
||||
|
||||
- bold
|
||||
- blink
|
||||
- reverseVideo
|
||||
- underline
|
||||
|
||||
Available colors are:
|
||||
|
||||
0. black
|
||||
1. red
|
||||
2. green
|
||||
3. yellow
|
||||
4. blue
|
||||
5. magenta
|
||||
6. cyan
|
||||
7. white
|
||||
|
||||
@ivar fg: Foreground colors accessed by attribute name, see above
|
||||
for possible names.
|
||||
|
||||
@ivar bg: Background colors accessed by attribute name, see above
|
||||
for possible names.
|
||||
"""
|
||||
fg = _textattributes._ColorAttribute(
|
||||
_textattributes._ForegroundColorAttr, _TEXT_COLORS)
|
||||
bg = _textattributes._ColorAttribute(
|
||||
_textattributes._BackgroundColorAttr, _TEXT_COLORS)
|
||||
|
||||
attrs = {
|
||||
'bold': insults.BOLD,
|
||||
'blink': insults.BLINK,
|
||||
'underline': insults.UNDERLINE,
|
||||
'reverseVideo': insults.REVERSE_VIDEO}
|
||||
|
||||
|
||||
|
||||
def assembleFormattedText(formatted):
|
||||
"""
|
||||
Assemble formatted text from structured information.
|
||||
|
||||
Currently handled formatting includes: bold, blink, reverse, underline and
|
||||
color codes.
|
||||
|
||||
For example::
|
||||
|
||||
from twisted.conch.insults.text import attributes as A
|
||||
assembleFormattedText(
|
||||
A.normal[A.bold['Time: '], A.fg.lightRed['Now!']])
|
||||
|
||||
Would produce "Time: " in bold formatting, followed by "Now!" with a
|
||||
foreground color of light red and without any additional formatting.
|
||||
|
||||
@param formatted: Structured text and attributes.
|
||||
|
||||
@rtype: C{str}
|
||||
@return: String containing VT102 control sequences that mimic those
|
||||
specified by L{formatted}.
|
||||
|
||||
@see: L{twisted.conch.insults.text.attributes}
|
||||
@since: 13.1
|
||||
"""
|
||||
return _textattributes.flatten(
|
||||
formatted, helper._FormattingState(), 'toVT102')
|
||||
|
||||
|
||||
|
||||
attributes = _CharacterAttributes()
|
||||
|
||||
__all__ = ['attributes', 'flatten']
|
||||
|
|
@ -0,0 +1,868 @@
|
|||
# -*- test-case-name: twisted.conch.test.test_window -*-
|
||||
|
||||
"""
|
||||
Simple insults-based widget library
|
||||
|
||||
@author: Jp Calderone
|
||||
"""
|
||||
|
||||
import array
|
||||
|
||||
from twisted.conch.insults import insults, helper
|
||||
from twisted.python import text as tptext
|
||||
|
||||
class YieldFocus(Exception):
|
||||
"""Input focus manipulation exception
|
||||
"""
|
||||
|
||||
class BoundedTerminalWrapper(object):
|
||||
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, bytes):
|
||||
return self.terminal.write(bytes)
|
||||
|
||||
class Widget(object):
|
||||
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 == '\t':
|
||||
self.tabReceived(modifier)
|
||||
elif keyID == '\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):
|
||||
func = getattr(self, 'func_' + keyID.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 C{reactor.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('c', ' ' * 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, (x, y)):
|
||||
return self.contents[(self._width * y) + x]
|
||||
|
||||
def __setitem__(self, (x, y), value):
|
||||
self.contents[(self._width * y) + x] = value
|
||||
|
||||
def clear(self):
|
||||
self.contents = array.array('c', ' ' * 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)
|
||||
terminal.write(''.join(self.contents[self._width * i:self._width * i + self._width])[:width])
|
||||
|
||||
|
||||
def horizontalLine(terminal, y, left, right):
|
||||
terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
|
||||
terminal.cursorPosition(left, y)
|
||||
terminal.write(chr(0161) * (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 xrange(top, bottom):
|
||||
terminal.cursorPosition(x, n)
|
||||
terminal.write(chr(0170))
|
||||
terminal.selectCharacterSet(insults.CS_US, insults.G0)
|
||||
|
||||
|
||||
def rectangle(terminal, (top, left), (width, height)):
|
||||
terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
|
||||
|
||||
terminal.cursorPosition(top, left)
|
||||
terminal.write(chr(0154))
|
||||
terminal.write(chr(0161) * (width - 2))
|
||||
terminal.write(chr(0153))
|
||||
for n in range(height - 2):
|
||||
terminal.cursorPosition(left, top + n + 1)
|
||||
terminal.write(chr(0170))
|
||||
terminal.cursorForward(width - 2)
|
||||
terminal.write(chr(0170))
|
||||
terminal.cursorPosition(0, top + height - 1)
|
||||
terminal.write(chr(0155))
|
||||
terminal.write(chr(0161) * (width - 2))
|
||||
terminal.write(chr(0152))
|
||||
|
||||
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('\x1b[31m')
|
||||
rectangle(terminal, (0, 0), (width, height))
|
||||
terminal.write('\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 == '\r':
|
||||
self.onPress()
|
||||
|
||||
def render(self, width, height, terminal):
|
||||
terminal.cursorPosition(0, 0)
|
||||
if self.focused:
|
||||
terminal.write('\x1b[1m' + self.label + '\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 = ''
|
||||
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 == '\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 ' ')
|
||||
terminal.write(currentText[self.cursor+1:])
|
||||
terminal.write(' ' * (self.maxwidth - len(currentText) + 1))
|
||||
else:
|
||||
more = self.maxwidth - len(currentText)
|
||||
terminal.write(currentText + '_' * more)
|
||||
|
||||
def _renderText(self):
|
||||
return self.buffer
|
||||
|
||||
class PasswordInput(TextInput):
|
||||
def _renderText(self):
|
||||
return '*' * len(self.buffer)
|
||||
|
||||
class TextOutput(Widget):
|
||||
text = ''
|
||||
|
||||
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 + ' ' * (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:
|
||||
wrappedLines = tptext.greedyWrap(inputLines.pop(0), width)
|
||||
outputLines.extend(wrappedLines or [''])
|
||||
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
|
||||
|
||||
def xOffset():
|
||||
def get(self):
|
||||
return self._xOffset
|
||||
def set(self, value):
|
||||
if self._xOffset != value:
|
||||
self._xOffset = value
|
||||
self.repaint()
|
||||
return get, set
|
||||
xOffset = property(*xOffset())
|
||||
|
||||
def yOffset():
|
||||
def get(self):
|
||||
return self._yOffset
|
||||
def set(self, value):
|
||||
if self._yOffset != value:
|
||||
self._yOffset = value
|
||||
self.repaint()
|
||||
return get, set
|
||||
yOffset = property(*yOffset())
|
||||
|
||||
_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 = ' '
|
||||
terminal.write(ch)
|
||||
if n < width:
|
||||
terminal.write(' ' * (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 = u'\N{BLACK LEFT-POINTING TRIANGLE}'
|
||||
_right = u'\N{BLACK RIGHT-POINTING TRIANGLE}'
|
||||
_bar = u'\N{LIGHT SHADE}'
|
||||
_slider = u'\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 = u'\N{BLACK UP-POINTING TRIANGLE}'
|
||||
_down = u'\N{BLACK DOWN-POINTING TRIANGLE}'
|
||||
_bar = u'\N{LIGHT SHADE}'
|
||||
_slider = u'\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 xrange(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('\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('\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 == '\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 + (' ' * (width - len(text))))
|
||||
if n == self.renderOffset:
|
||||
terminal.restoreCursor()
|
||||
Loading…
Add table
Add a link
Reference in a new issue