Comment faire défiler du texte en Python / Curses subwindow?
dans mon script Python qui utilise des Malédictions, j'ai un subwin auquel du texte est affecté. Parce que la longueur du texte peut être plus longue que la taille de la fenêtre, le texte doit être déplacé.
il ne semble pas qu'il y ait un attribut CSS-"overflow" comme attribut pour Curses windows. Les docs Python/malédictions sont aussi assez cryptiques sur cet aspect.
est-ce que quelqu'un ici a une idée de comment je peux coder une sous-fenêtre scrollable Curses en utilisant Python et réellement faire défiler il?
edit: question plus précise
5 réponses
OK avec fenêtre.faites défiler jusqu'c'était trop compliqué de déplacer le contenu de la fenêtre. Au lieu de cela, les malédictions.newpad a fait pour moi.
Créer un pad:
mypad = curses.newpad(40,60)
mypad_pos = 0
mypad.refresh(mypad_pos, 0, 5, 5, 10, 60)
Ensuite, vous pouvez faire défiler en augmentant/diminuant mypad_pos en fonction de l'entrée de la fenêtre.getch() dans 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)
droite, j'étais un peu confus sur la façon d'utiliser les pads (afin de faire défiler du texte), et je ne pouvais toujours pas le comprendre après avoir lu ce post; d'autant plus que je voulais l'utiliser dans un contexte où le contenu est un "tableau de lignes"existant. J'ai donc préparé un petit exemple, qui montre des similitudes (et les différences) entre newpad
et 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)
quand vous lancez ceci, au début vous obtiendrez quelque chose comme (newpad
gauche, subpad
droite):
┌────────────────────────┐ ┌────────────────────────┐
│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 │
└────────────────────────┘
Quelques remarques:
newpad
etsubpad
devraient avoir leur largeur/hauteur de taille pour le contenu (nombre de lignes/max largeur de ligne de la matrice de lignes) + éventuelle frontière de l'espace- dans les deux cas, vous pouvez autoriser des lignes supplémentaires avec
scrollok()
- mais pas d'une largeur - dans les deux cas, vous "poussez" essentiellement une ligne au bas du bloc-notes; et puis
scroll()
jusqu'à faire de la place pour la prochaine - Les
refresh
méthodenewpad
a, permet alors juste une région de ce "contenu entier" à afficher à l'écran;subpad
plus-moins doit être indiqué dans la taille, il a été instancié dans - si vous dessinez les bordures des pads avant d'ajouter des chaînes de contenu-alors les bordures défileront aussi (c'est le
───
pièce montrée à l'...Line 1 ───│
partie).
liens Utiles:
C'est la réponse à cette question: Comment faire un menu déroulant en python-malédictions
ce code vous permet de créer un petit menu défilant dans une boîte à partir d'une liste de chaînes.
Vous pouvez également utiliser ce code pour obtenir la liste des chaînes à partir d'une requête sqlite ou d'un fichier csv.
Pour modifier le nombre maximum de lignes du menu, vous avez juste à modifier max_row
.
Si vous appuyez sur entrée, le programme affichera la chaîne sélectionnée valeur et sa position.
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()
je voulais utiliser un pavé de défilement pour afficher le contenu de quelques gros fichiers de texte mais cela n'a pas bien fonctionné parce que les textes peuvent avoir des sauts de ligne et il était assez difficile de comprendre combien de caractères afficher à la fois pour correspondre au bon nombre de colonnes et de lignes.
J'ai donc décidé de diviser d'abord mes fichiers texte en lignes de caractères exactement colonnes, en se remplaçant par des espaces lorsque les lignes étaient trop courtes. Faites défiler le texte à devenir plus facile.
Voici un exemple de code pour afficher n'importe quel fichier texte:
#!/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)
L'astuce principale est la ligne:
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()]))
tout d'abord, les tabulations dans le texte sont converties en espaces, puis j'ai utilisé la méthode splitlines() pour convertir mon texte en tableau de lignes. Mais certaines lignes peuvent être plus longues que le nombre de colonnes, donc j'ai divisé chaque ligne en plusieurs caractères de colonnes et j'ai utilisé reduce pour transformer la liste résultante en une liste de lignes. Enfin, j'ai utilisé carte pour garnir chaque ligne avec des espaces de fuite de sorte que sa longueur est exactement les caractères des colonnes.
J'espère que cela vous aidera.