Código fuente para pynprcalc.calc.ui
"""
Interfaz de usuario en línea de comandos.
Utiliza *ncurses*.
"""
import curses
import curses.textpad
from . import main
[documentos]def run():
"""
Correr la interfaz.
"""
ui = UI()
[documentos]class UI:
"""
Controla la interfaz de usuario.
"""
def __init__(self):
self._terminado = False # indica cuando salir del loop principal
self._calc = main.Calc()
# encierro en try..finally porque en el caso de error debo cerrar
# *curses* correctamente, de lo contrario la terminal queda desacomodada
try:
self._terminal = _Terminal()
self._loop()
finally:
self._terminal.cerrar()
print("Hay algo mal que no anda bien")
[documentos] def _loop(self):
"""
Loop principal del programa.
"""
while not self._terminado:
self._imprimir_stack()
self._procesar_entrada()
[documentos] def _imprimir_stack(self):
"""
Limpia la pantalla y muestra el contenido del *stack*.
"""
self._terminal.limpiar()
lista = self._calc.lista()
for i, n in enumerate(lista):
# dejar una linea en blanco y una para el cuadro de texto, dibujar
# de arriba a abajo
y = self._terminal.alto - 2 - len(lista) + i
self._terminal.escribir(0, y, str(n))
self._terminal.actualizar()
[documentos] def _procesar_entrada(self):
"""
Permite al usuario ingresar números o comandos.
En el caso que el comando ingresado sea inválido, vuelve a imprimir el
*stack*, muestra un mensaje de error y espera un nuevo intento.
"""
terminado = False
while not terminado:
comandos = self._terminal.leer_entrada().strip().split(" ")
try:
for comando in comandos:
self._calc.ejecutar(comando)
except ValueError as e:
# mostrar error arriba de la pantalla
y = self._terminal.alto - 2
self._imprimir_stack()
self._terminal.escribir(0, y, str(e), curses.A_REVERSE)
self._terminal.actualizar()
else:
terminado = True
[documentos]class _Terminal:
"""
Implementa el código relacionado a *curses*.
*curses* es una librería que permite trabajar con la terminal, pero como es
bastante engorrosa de usar, meto todo lo que necesito en esta clase.
Implementa una ventana en donde se puede escribir texto, y la última línea
es un cuadro de texto en donde el usuario puede escribir.
Attributes:
alto (int): Alto de la pantalla en líneas.
ancho (int): Ancho de la pantalla en columnas.
"""
def __init__(self):
# crear pantalla
self._stdscr = curses.initscr()
self.alto, self.ancho = self._stdscr.getmaxyx()
# no dejar al usuario escribir en pantalla, solamente cuando nosotros
# queramos
curses.noecho()
# dejar que curses maneje teclas como *Re Pag*
self._stdscr.keypad(True)
# crear cuadro de texto abajo de la pantalla
self._editwin = curses.newwin(1, self.ancho, self.alto - 1, 0)
self._editbox = curses.textpad.Textbox(self._editwin)
[documentos] def limpiar(self):
"""
Limpiar la pantalla.
No limpia el cuadro de texto.
"""
self._stdscr.clear()
[documentos] def escribir(self, x, y, texto, estilo=curses.A_NORMAL):
"""
Escribir texto en una posición específica de la pantalla.
Opcionalmente se puede especificar un estilo para el texto. Los estilos
deben ser algunas de las constantes presentes en *curses*, como por
ejemplo ``curses.A_BOLD``.
"""
self._stdscr.addstr(y, x, texto, estilo)
[documentos] def leer_entrada(self):
"""
Dar foco al cuadro de texto y devolver lo ingresado por el usuario.
Da foco al cuadro de texto y bloquea hasta que el usuario haya terminado
de escribir, luego devuelve lo ingresado y borra el cuadro de texto.
"""
self._editbox.edit() # lee entrada y bloquea hasta que se presione Enter
entrada = self._editbox.gather()
self._editwin.clear()
return entrada
[documentos] def actualizar(self):
"""
Refresca la pantalla para que se vean reflejados los cambios.
También refresca el cuadro de texto.
"""
self._stdscr.refresh()
self._editwin.refresh()
[documentos] def cerrar(self):
"""
Cierra *curses* correctamente.
Como *curses* cambia el comportamiento de la terminal, al cerrar hay que
revertir los cambios sino la terminal queda medio desacomodada.
"""
curses.nocbreak()
self._stdscr.keypad(False)
curses.echo()
curses.endwin()