2010-03-25 7 views
17

Dans mon script Python qui utilise des Curses, j'ai un subwin auquel du texte est assigné. Parce que la longueur du texte peut être plus longue que la taille de la fenêtre, le texte devrait être défilable.Comment faire défiler le texte dans Python/Curses subwindow?

Il ne semble pas qu'il y ait un attribut CSS "overflow" pour les fenêtres Curses. Les docs Python/Curses sont également plutôt cryptiques sur cet aspect.

Est-ce que quelqu'un ici a une idée de comment je peux coder une sous-fenêtre Curses défilante en utilisant Python et le faire défiler?

\ edit: plus question précise

Répondre

22

OK avec window.scroll il était trop compliqué de déplacer le contenu de la fenêtre. Au lieu de cela, curses.newpad l'a fait pour moi.

Créer un tampon:

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 window.getch() 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) 
1

Réglez le window.scrollok (Vrai).

Documentation

+4

Cela fait que la fenêtre accepte les textes qui dépassent sa propre taille. Il est possible de faire défiler en utilisant window.scroll (1). Mais alors les lignes défilées doivent être redessinées, ce qui n'est pas expliqué dans les docs. Donc, obtenir Curses défilement des fenêtres prend plusieurs étapes, dont certaines me manque encore ... – lecodesportif

4

droit, j'étais un peu confus sur la façon d'utiliser les pads (afin de faire défiler le texte), et ne pouvait toujours pas comprendre après avoir lu ce post; d'autant plus que je voulais l'utiliser dans le contexte d'un «tableau de lignes» existant. Alors j'ai 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) 

Lorsque vous exécutez ce, dans un premier temps, 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 notes:

  • les deux newpad et subpad devraient avoir leur taille largeur/hauteur d au contenu (lignes num/largeur de ligne max du réseau de lignes) + espace éventuel frontière
  • Dans les deux cas, vous pourriez permettre à des lignes supplémentaires avec scrollok() - mais pas surlargeur
  • Dans les deux cas, vous essentiellement " Poussez "une ligne au bas du coussin; puis scroll() jusqu'à faire de la place pour la prochaine
  • La méthode refresh spéciale qui a newpad, permet alors seulement une région de ce « contenu entier » à afficher à l'écran; subpad plus-moins doit être affiché dans la taille où il a été instancié
  • Si vous dessinez les bordures des pads avant d'ajouter des chaînes de contenu, les bordures défileront également (c'est la pièce ─── affichée à la partie ...Line 1 ───│) .

Liens utiles:

0

Voici la réponse de cette question: How to make a scrolling menu in python-curses

Ce code vous permet de créer un petit menu déroulant 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, il vous suffit d'éditer max_row.
Si vous appuyez sur Entrée, le programme imprimera la valeur de chaîne sélectionnée 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() 
0

Je voulais utiliser un tampon de défilement pour afficher le contenu de certains fichiers texte volumineux mais cela n'a pas bien fonctionné parce que les textes peuvent avoir des sauts de ligne et il était assez difficile de savoir combien de caractères pour afficher à le temps de s'adapter au bon nombre de colonnes et de rangées. J'ai donc décidé de scinder d'abord mes fichiers texte en lignes de caractères COLUMNS, en complétant des espaces lorsque les lignes étaient trop courtes. Ensuite, le défilement du texte devient plus facile.

Voici un exemple de code pour afficher un 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()])) 

En premier lieu, les tableaux dans le texte sont convertis en espaces, je méthode splitlines() pour convertir mon texte en tableau de lignes. Mais certaines lignes peuvent être plus longues que notre numéro COLUMNS, j'ai donc divisé chaque ligne en un morceau de caractères COLUMNS et ensuite utilisé reduce pour transformer la liste résultante en une liste de lignes. Enfin, j'ai utilisé la carte pour remplir chaque ligne avec des espaces de fin pour que sa longueur soit exactement des caractères COLONNES.

Espérons que cela aide.

Questions connexes