There is a way to make a scrolling menu in python-curses? I have a list of records that I got from a query in sqlite3 and I have to show them in a box but they are more than the max number of rows: can I make a little menu to show them all without making curses crashing?
问题:
回答1:
This code allows you to create a little menu in a box from a list of strings.
You can also use this code getting the list of strings from a sqlite query or from a csv file.
To edit the max number of rows of the menu you just have to edit max_row
.
If you press enter the program will print the selected string value and its 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()
回答2:
To make a scrollable widget that can scroll through text that is larger than a screenful you will need to use curses.newpad
You can find a simple example here: https://stackoverflow.com/a/2523020/9205341
And the Python 3/Python 2 docs there.
回答3:
use this skeleton but add page-up / -down to display entries in groups of 10.
very crash-proof in python2 and python3 !
#!/usr/bin/env python2
# -*- coding: UTF-8 -*-
#kate: syntax Python ;
# a skeleton menu with python2 & py3 https://paste.cutelyst.org/
# https://0bin.net/paste/yauebNkDNbwgZqwy#a2YfWpSTHh0RV-0Yfz3FIypudIU6oi4DWvV9EGXo1Pv
'''function 1 on F1 1 and NUM-1'''
def funkt1():
return 1
def funkt2():
return 2
def funkt3():
return 3
def funkt4():
return 4
def funkt5():
return 5
def funkt6():
return 6
def funkt7():
return 7
def funkt8():
return 8
def funkt9():
return 9
import sys,os
import curses
global menuE
global e
global noofmes
global keychar
e = 0
menuE = 1 # active entry
noofmes = 10 # number of menu entries +1
keychar = " " # like 1 or 2 ...
class switch(object):
value = None
def __new__(class_, value):
class_.value = value
return True
def case(*args):
return any((arg == switch.value for arg in args))
def draw_menu(stdscr):
global menuE
global e
global noofmes
global keychar
k = 0
cursor_x = 0
cursor_y = 0
# Clear and refresh the screen for a blank canvas
stdscr.clear()
stdscr.refresh()
# Start colors in curses
curses.start_color()
curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE)
# Loop where k is the last character pressed
while (k != ord('q')):
# Initialization
stdscr.clear()
height, width = stdscr.getmaxyx()
ki = int(k)
try: keychar = chr(k)
except: keychar = 0
callfunc = False
while switch(k):
if case(49, 265, 360): # allows keys 1 or NUM-1 or F1 to be Entry1 for 0..9
menuE = 1
break
if case(50, 266 ): # 258 mouse u
menuE = 2
break
if case(51, 267, 338):
menuE = 3
break
if case(52, 268 ): #260
menuE = 4
break
if case(53, 269, 69):
menuE = 5
break
if case(54, 270 ): #261
menuE = 6
break
if case(55, 271, 262):
menuE = 7
break
if case(56, 272 ): #259 mouse d
menuE = 8
break
if case(57, 273, 339):
menuE = 9
break
if case(10, 32 , 83 ): # SPACE , ENTER , 83 mouse middle
callfunc = True
break
pass
break
if callfunc:
if menuE == 1: e = funkt1()
elif menuE == 2: e = funkt2()
elif menuE == 3: e = funkt3()
elif menuE == 4: e = funkt4()
elif menuE == 5: e = funkt5()
elif menuE == 6: e = funkt6()
elif menuE == 7: e = funkt7()
elif menuE == 8: e = funkt8()
elif menuE == 9: e = funkt9()
if k == curses.KEY_DOWN:
cursor_y = cursor_y + 1
menuE = (menuE + 1) % noofmes
elif k == curses.KEY_UP:
cursor_y = cursor_y - 1
menuE = (menuE - 1) % noofmes
elif k == curses.KEY_RIGHT:
cursor_x = cursor_x + 1
elif k == curses.KEY_LEFT:
cursor_x = cursor_x - 1
cursor_x = max(0, cursor_x)
cursor_x = min(width-1, cursor_x)
cursor_y = max(0, cursor_y)
cursor_y = min(height-1, cursor_y)
# Declaration of strings
title = "Python2 , py3 menu demo with lib ´curses´ "[:width-1]
subtitle = "because py2 will never die!"[:width-1]
keystr = str(e) + " <-- funkt " + "Last key pressed: {}".format(k)[:width-1]
statusbarstr = "Press 'q' to exit | STATUS BAR | Pos: {}, {}".format(cursor_x, cursor_y)
if k == 0:
keystr = "No key press detected..."[:width-1]
# Centering calculations
start_x_title = int(( width // 2) - (len(title) // 2) - len(title) % 2)
start_x_subtitle = int(( width // 2) - (len(subtitle) // 2) - len(subtitle) % 2)
start_x_keystr = int(( width // 2) - (len(keystr) // 2) - len(keystr) % 2)
start_y = int(( height // 2) - 2)
# Rendering some text
whstr = "Width: {}, Height: {}".format(width, height)
stdscr.addstr(0, 0, whstr, curses.color_pair(1))
# Render status bar
stdscr.attron(curses.color_pair(3))
stdscr.addstr(height-1, 0, statusbarstr)
stdscr.addstr(height-1, len(statusbarstr), " " * (width - len(statusbarstr) - 1))
stdscr.attroff(curses.color_pair(3))
# Turning on attributes for title
stdscr.attron(curses.color_pair(2))
stdscr.attron(curses.A_BOLD)
# Rendering title
stdscr.addstr(start_y -6, start_x_title, title)
# Turning off attributes for title
stdscr.attroff(curses.color_pair(2))
stdscr.attroff(curses.A_BOLD)
# Print rest of text
stdscr.addstr( start_y - 4, start_x_subtitle, subtitle)
#stdscr.addstr(start_y - 3, (width // 2) - 2, '-' * 4)
stdscr.addstr( start_y - 2, start_x_keystr, keystr)
# menu
stdscr.addstr(start_y + 0, start_x_subtitle, "~~~ Menu ~~~")
if menuE == 1 :
stdscr.attron( curses.A_BOLD)
else : stdscr.attroff(curses.A_BOLD)
stdscr.addstr(start_y + 1, start_x_subtitle, "1 Entry1")
if menuE == 2 :
stdscr.attron( curses.A_BOLD)
else : stdscr.attroff(curses.A_BOLD)
stdscr.addstr(start_y + 2, start_x_subtitle, "2 Entry2")
if menuE == 3 :
stdscr.attron( curses.A_BOLD)
else : stdscr.attroff(curses.A_BOLD)
stdscr.addstr(start_y + 3 , start_x_subtitle, "3 Entry3")
if menuE == 4 :
stdscr.attron( curses.A_BOLD)
else : stdscr.attroff(curses.A_BOLD)
stdscr.addstr(start_y + 4 , start_x_subtitle, "4 Entry4")
if menuE == 5 :
stdscr.attron( curses.A_BOLD)
else : stdscr.attroff(curses.A_BOLD)
stdscr.addstr(start_y + 5 , start_x_subtitle, "5 Entry5")
if menuE == 6 :
stdscr.attron( curses.A_BOLD)
else : stdscr.attroff(curses.A_BOLD)
stdscr.addstr(start_y + 6 , start_x_subtitle, "6 Entry6")
if menuE == 7 :
stdscr.attron( curses.A_BOLD)
else : stdscr.attroff(curses.A_BOLD)
stdscr.addstr(start_y + 7 , start_x_subtitle, "7 Entry7")
if menuE == 8 :
stdscr.attron( curses.A_BOLD)
else : stdscr.attroff(curses.A_BOLD)
stdscr.addstr(start_y + 8 , start_x_subtitle, "8 Entry8")
if menuE == 9 :
stdscr.attron( curses.A_BOLD)
else : stdscr.attroff(curses.A_BOLD)
stdscr.addstr(start_y + 9 , start_x_subtitle, "9 Entry9")
stdscr.move(cursor_y, cursor_x)
# Refresh the screen
stdscr.refresh()
# Wait for next input
k = stdscr.getch()
def main():
curses.wrapper(draw_menu)
if __name__ == "__main__":
main()
回答4:
I used https://github.com/wong2/pick
>>> title = 'Please choose your favorite programming language (press SPACE to mark, ENTER to continue): '
>>> options = ['Java', 'JavaScript', 'Python', 'PHP', 'C++', 'Erlang', 'Haskell']
>>> pick(options, title, multi_select=True, min_selection_count=1)
It creates an ncurses-based picker that takes up the entire terminal window and lets you select multiple options (it'll scroll the options if they don't all fit on the page). After you chose stuff it returns the values and their indeces:
[('Java', 0), ('C++', 4)]