python3 - tutorial curses python
¿Cómo desplazar el texto en Python/Curses subwindow? (5)
En mi secuencia de comandos de Python, que usa cursas, tengo un subwin al que se le asigna algún texto. Debido a que la longitud del texto puede ser más larga que el tamaño de la ventana, el texto debe ser desplazable.
No parece que haya ningún atributo similar a "desbordamiento" de CSS para las ventanas Curses. Los documentos de Python / Curses también son bastante crípticos en este aspecto.
¿Alguien aquí tiene una idea de cómo puedo codificar una subventana de Curses desplazable utilizando Python y desplazarme por ella?
/ edit: pregunta mas precisa
Correcto, estaba un poco confundido sobre cómo utilizar las almohadillas (para desplazar el texto), y aún no pude entenderlo después de leer esta publicación; especialmente porque quería usarlo en un contexto en el que el contenido es un "conjunto de líneas" existente. Así que preparé un pequeño ejemplo, que muestra similitudes (y diferencias) entre newpad
y subpad
:
#!/usr/bin/env python2.7
import curses
# content - array of lines (list)
mylines = ["Line {0} ".format(id)*3 for id in range(1,11)]
import pprint
pprint.pprint(mylines)
def main(stdscr):
hlines = begin_y = begin_x = 5 ; wcols = 10
# calculate total content size
padhlines = len(mylines)
padwcols = 0
for line in mylines:
if len(line) > padwcols: padwcols = len(line)
padhlines += 2 ; padwcols += 2 # allow border
stdscr.addstr("padhlines "+str(padhlines)+" padwcols "+str(padwcols)+"; ")
# both newpad and subpad are <class ''_curses.curses window''>:
mypadn = curses.newpad(padhlines, padwcols)
mypads = stdscr.subpad(padhlines, padwcols, begin_y, begin_x+padwcols+4)
stdscr.addstr(str(type(mypadn))+" "+str(type(mypads)) + "/n")
mypadn.scrollok(1)
mypadn.idlok(1)
mypads.scrollok(1)
mypads.idlok(1)
mypadn.border(0) # first ...
mypads.border(0) # ... border
for line in mylines:
mypadn.addstr(padhlines-1,1, line)
mypadn.scroll(1)
mypads.addstr(padhlines-1,1, line)
mypads.scroll(1)
mypadn.border(0) # second ...
mypads.border(0) # ... border
# refresh parent first, to render the texts on top
#~ stdscr.refresh()
# refresh the pads next
mypadn.refresh(0,0, begin_y,begin_x, begin_y+hlines, begin_x+padwcols)
mypads.refresh()
mypads.touchwin()
mypadn.touchwin()
stdscr.touchwin() # no real effect here
#stdscr.refresh() # not here! overwrites newpad!
mypadn.getch()
# even THIS command erases newpad!
# (unless stdscr.refresh() previously):
stdscr.getch()
curses.wrapper(main)
Cuando newpad
esto, al principio obtendrás algo como ( newpad
izquierdo, subpad
derecho):
┌────────────────────────┐ ┌────────────────────────┐
│Line 1 Line 1 Line 1 ───│ │Line 1 Line 1 Line 1 ───│
│Line 2 Line 2 Line 2 │ │Line 2 Line 2 Line 2 │
│Line 3 Line 3 Line 3 │ │Line 3 Line 3 Line 3 │
│Line 4 Line 4 Line 4 │ │Line 4 Line 4 Line 4 │
│Line 5 Line 5 Line 5 │ │Line 5 Line 5 Line 5 │
│Line 6 Line 6 Line 6 │
│Line 7 Line 7 Line 7 │
│Line 8 Line 8 Line 8 │
│Line 9 Line 9 Line 9 │
│Line 10 Line 10 Line 10 │
└────────────────────────┘
Algunas notas:
- Tanto el
newpad
como elsubpad
deben tener su ancho / altosubpad
al contenido (número de líneas / ancho máximo de línea de la matriz de líneas) + espacio de borde eventual - En ambos casos, puede permitir líneas adicionales con
scrollok()
, pero no ancho adicional - En ambos casos, básicamente "presionas" una línea en la parte inferior de la almohadilla; y luego
scroll()
hacia arriba para dejar espacio para la próxima - El método de
refresh
especial que tiene elnewpad
permite que solo se muestre en pantalla una región de este "contenido completo";subpad
more-less debe mostrarse en el tamaño en el que se creó la instancia. - Si dibuja los bordes de las almohadillas antes de agregar cadenas de contenido, entonces los bordes también se desplazarán hacia arriba (esa es la pieza shown que se muestra en la parte
...Line 1 ───│
).
Enlaces útiles:
De acuerdo con window.scroll, era demasiado complicado mover el contenido de la ventana. En cambio, maldes.newpad lo hizo por mí.
Crea un pad:
mypad = curses.newpad(40,60)
mypad_pos = 0
mypad.refresh(mypad_pos, 0, 5, 5, 10, 60)
Luego puede desplazarse aumentando / disminuyendo mypad_pos dependiendo de la entrada de window.getch () en cmd:
if cmd == curses.KEY_DOWN:
mypad_pos += 1
mypad.refresh(mypad_pos, 0, 5, 5, 10, 60)
elif cmd == curses.KEY_UP:
mypad_pos -= 1
mypad.refresh(mypad_pos, 0, 5, 5, 10, 60)
Esta es la respuesta a esta pregunta: cómo hacer un menú de desplazamiento en python-curses
Este código le permite crear un pequeño menú de desplazamiento en un cuadro de una lista de cadenas.
También puede usar este código para obtener la lista de cadenas de una consulta sqlite o de un archivo csv.
Para editar el número máximo de filas del menú, solo tiene que editar max_row
.
Si pulsa Intro, el programa imprimirá el valor de la cadena seleccionada y su posición.
from __future__ import division #You don''t need this in Python3
import curses
from math import *
screen = curses.initscr()
curses.noecho()
curses.cbreak()
curses.start_color()
screen.keypad( 1 )
curses.init_pair(1,curses.COLOR_BLACK, curses.COLOR_CYAN)
highlightText = curses.color_pair( 1 )
normalText = curses.A_NORMAL
screen.border( 0 )
curses.curs_set( 0 )
max_row = 10 #max number of rows
box = curses.newwin( max_row + 2, 64, 1, 1 )
box.box()
strings = [ "a", "b", "c", "d", "e", "f", "g", "h", "i", "l", "m", "n" ] #list of strings
row_num = len( strings )
pages = int( ceil( row_num / max_row ) )
position = 1
page = 1
for i in range( 1, max_row + 1 ):
if row_num == 0:
box.addstr( 1, 1, "There aren''t strings", highlightText )
else:
if (i == position):
box.addstr( i, 2, str( i ) + " - " + strings[ i - 1 ], highlightText )
else:
box.addstr( i, 2, str( i ) + " - " + strings[ i - 1 ], normalText )
if i == row_num:
break
screen.refresh()
box.refresh()
x = screen.getch()
while x != 27:
if x == curses.KEY_DOWN:
if page == 1:
if position < i:
position = position + 1
else:
if pages > 1:
page = page + 1
position = 1 + ( max_row * ( page - 1 ) )
elif page == pages:
if position < row_num:
position = position + 1
else:
if position < max_row + ( max_row * ( page - 1 ) ):
position = position + 1
else:
page = page + 1
position = 1 + ( max_row * ( page - 1 ) )
if x == curses.KEY_UP:
if page == 1:
if position > 1:
position = position - 1
else:
if position > ( 1 + ( max_row * ( page - 1 ) ) ):
position = position - 1
else:
page = page - 1
position = max_row + ( max_row * ( page - 1 ) )
if x == curses.KEY_LEFT:
if page > 1:
page = page - 1
position = 1 + ( max_row * ( page - 1 ) )
if x == curses.KEY_RIGHT:
if page < pages:
page = page + 1
position = ( 1 + ( max_row * ( page - 1 ) ) )
if x == ord( "/n" ) and row_num != 0:
screen.erase()
screen.border( 0 )
screen.addstr( 14, 3, "YOU HAVE PRESSED ''" + strings[ position - 1 ] + "'' ON POSITION " + str( position ) )
box.erase()
screen.border( 0 )
box.border( 0 )
for i in range( 1 + ( max_row * ( page - 1 ) ), max_row + 1 + ( max_row * ( page - 1 ) ) ):
if row_num == 0:
box.addstr( 1, 1, "There aren''t strings", highlightText )
else:
if ( i + ( max_row * ( page - 1 ) ) == position + ( max_row * ( page - 1 ) ) ):
box.addstr( i - ( max_row * ( page - 1 ) ), 2, str( i ) + " - " + strings[ i - 1 ], highlightText )
else:
box.addstr( i - ( max_row * ( page - 1 ) ), 2, str( i ) + " - " + strings[ i - 1 ], normalText )
if i == row_num:
break
screen.refresh()
box.refresh()
x = screen.getch()
curses.endwin()
exit()
Establecer el window.scrollok (True).
Quería usar un panel de desplazamiento para mostrar el contenido de algunos archivos de texto grandes, pero esto no funcionó bien porque los textos pueden tener saltos de línea y fue bastante difícil averiguar cuántos caracteres se mostrarán a la vez para adaptarse al buen número de caracteres. Columnas y filas.
Así que decidí dividir primero mis archivos de texto en líneas de exactamente COLUMNAS, rellenando con espacios cuando las líneas eran demasiado cortas. Luego, desplazar el texto se vuelve más fácil.
Aquí hay un código de ejemplo para mostrar cualquier archivo de texto:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import curses
import locale
import sys
def main(filename, filecontent, encoding="utf-8"):
try:
stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
curses.curs_set(0)
stdscr.keypad(1)
rows, columns = stdscr.getmaxyx()
stdscr.border()
bottom_menu = u"(↓) Next line | (↑) Previous line | (→) Next page | (←) Previous page | (q) Quit".encode(encoding).center(columns - 4)
stdscr.addstr(rows - 1, 2, bottom_menu, curses.A_REVERSE)
out = stdscr.subwin(rows - 2, columns - 2, 1, 1)
out_rows, out_columns = out.getmaxyx()
out_rows -= 1
lines = map(lambda x: x + " " * (out_columns - len(x)), reduce(lambda x, y: x + y, [[x[i:i+out_columns] for i in xrange(0, len(x), out_columns)] for x in filecontent.expandtabs(4).splitlines()]))
stdscr.refresh()
line = 0
while 1:
top_menu = (u"Lines %d to %d of %d of %s" % (line + 1, min(len(lines), line + out_rows), len(lines), filename)).encode(encoding).center(columns - 4)
stdscr.addstr(0, 2, top_menu, curses.A_REVERSE)
out.addstr(0, 0, "".join(lines[line:line+out_rows]))
stdscr.refresh()
out.refresh()
c = stdscr.getch()
if c == ord("q"):
break
elif c == curses.KEY_DOWN:
if len(lines) - line > out_rows:
line += 1
elif c == curses.KEY_UP:
if line > 0:
line -= 1
elif c == curses.KEY_RIGHT:
if len(lines) - line >= 2 * out_rows:
line += out_rows
elif c == curses.KEY_LEFT:
if line >= out_rows:
line -= out_rows
finally:
curses.nocbreak(); stdscr.keypad(0); curses.echo(); curses.curs_set(1)
curses.endwin()
if __name__ == ''__main__'':
locale.setlocale(locale.LC_ALL, '''')
encoding = locale.getpreferredencoding()
try:
filename = sys.argv[1]
except:
print "Usage: python %s FILENAME" % __file__
else:
try:
with open(filename) as f:
filecontent = f.read()
except:
print "Unable to open file %s" % filename
else:
main(filename, filecontent, encoding)
El truco principal es la línea:
lines = map(lambda x: x + " " * (out_columns - len(x)), reduce(lambda x, y: x + y, [[x[i:i+out_columns] for i in xrange(0, len(x), out_columns)] for x in filecontent.expandtabs(4).splitlines()]))
Primero, las tabulaciones en el texto se convierten en espacios, luego usé el método splitlines () para convertir mi texto en una matriz de líneas. Pero algunas líneas pueden ser más largas que nuestro número de COLUMNAS, por lo que dividí cada línea en una porción de caracteres de COLUMNAS y luego usé Reducir para transformar la lista resultante en una lista de líneas. Finalmente, utilicé el mapa para rellenar cada línea con espacios al final, de modo que su longitud sea exactamente de los caracteres de COLUMNAS.
Espero que esto ayude.