Como rolar o texto na subjanela Python / Curses?

No meu script Python que usa Curses, eu tenho um subwin ao qual algum texto é atribuído. Como o comprimento do texto pode ser maior que o tamanho da janela, o texto deve ser rolável.

Não parece que exista qualquer atributo de “estouro” do CSS para as janelas Curses. Os documentos do Python / Curses também são bem crípticos nesse aspecto.

Alguém aqui tem uma idéia de como eu posso codificar uma subjanela de Curses rolável usando Python e realmente rolar através dela?

\ edite: pergunta mais precisa

OK com window.scroll era muito complicado para mover o conteúdo da janela. Em vez disso, curses.newpad fez isso por mim.

Crie um pad:

mypad = curses.newpad(40,60) mypad_pos = 0 mypad.refresh(mypad_pos, 0, 5, 5, 10, 60) 

Então você pode rolar aumentando / diminuindo mypad_pos dependendo da input de window.getch () em 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) 

Certo, eu estava um pouco confuso sobre como utilizar pads (para rolar o texto), e ainda não consegui descobrir depois de ler este post; especialmente desde que eu queria usá-lo em um contexto do conteúdo sendo um “array de linhas” existente. Então eu preparei um pequeno exemplo, que mostra semelhanças (e diferenças) entre o newpad e o 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 : 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) 

Quando você executa isso, a princípio, você obterá algo como ( newpad left, subpad right):

  ┌────────────────────────┐ ┌────────────────────────┐ │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 │ └────────────────────────┘ 

Algumas notas:

  • Tanto o newpad quanto o subpad devem ter sua largura / altura dimensionada para o conteúdo (num lines / max line width da matriz de linhas) + eventual espaço de borda
  • Em ambos os casos, você pode permitir linhas extras com scrollok() – mas não largura extra
  • Em ambos os casos, você basicamente “empurra” uma linha na parte inferior do bloco; e, em seguida, scroll() até abrir espaço para a próxima
  • O método de refresh especial que o newpad possui permite que apenas uma região desse “conteúdo inteiro” seja mostrada na canvas; subpad mais menos tem que ser mostrado no tamanho que foi instanciado em
  • Se você desenhar as bordas dos blocos antes de adicionar seqüências de conteúdo – então as bordas irão rolar para cima também (isto é, a peça ─── mostrada na ...Line 1 ───│ parte).

Links Úteis:

Defina o window.scrollok (True).

Documentação

Esta é a resposta desta pergunta: Como fazer um menu de rolagem em python-curses

Esse código permite que você crie um pequeno menu de rolagem em uma checkbox de uma lista de seqüências de caracteres.
Você também pode usar esse código obtendo a lista de seqüências de caracteres de uma consulta de sqlite ou de um arquivo csv.
Para editar o número máximo de linhas do menu, você só precisa editar o max_row .
Se você pressionar enter, o programa imprimirá o valor da string selecionada e sua posição.

 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() 

Eu queria usar um bloco de rolagem para exibir o conteúdo de alguns arquivos de texto grandes, mas isso não funcionou bem, porque os textos podem ter quebras de linha e era muito difícil descobrir quantos caracteres exibir de cada vez para se ajustar ao bom número de colunas e linhas.

Então, decidi primeiro dividir meus arquivos de texto em linhas de caracteres exatamente COLUMNS, preenchendo com espaços quando as linhas eram muito curtas. Em seguida, a rolagem do texto fica mais fácil.

Aqui está um exemplo de código para exibir qualquer arquivo 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) 

O truque principal é a linha:

 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()])) 

Primeiro, tabulações no texto são convertidas em espaços, então eu usei o método splitlines () para converter meu texto em uma matriz de linhas. Mas algumas linhas podem ser maiores que nosso número COLUMNS, então dividi cada linha em um fragment de caracteres COLUMNS e depois usei reduce para transformar a lista resultante em uma lista de linhas. Por fim, usei o mapa para preencher cada linha com espaços à direita, para que seu comprimento seja exatamente igual a COLUMNS.

Espero que isto ajude.

    Intereting Posts