Автор — Грэг Валтерс (Greg Walters)

В этом месяце мы поговорим об использовании Curses в Python. Нет, мы не собираемся использовать Python для ругательств грязными словечками, однако и такое возможно, если вы действительно захотите. Мы говорим об использовании библиотеки Curses с целью приукрасить окно вывода.

Если вы застали времена первых компьютеров, вы вспомните, что в бизнесе всегда использовались мейнфреймы — с простейшими терминалами ввода-вывода (экраны и клавиатуры). К одному компьютеру могли быть подключены несколько терминалов. Проблема в том, что терминалы были достаточно «скромными» устройствами. У них не было ни окон, ни цветов, ни многого другого — всего лишь 24 строчки по 80 символов (это в лучшем случае). Когда же персональные компьютеры стали популярными, в старые добрые времена DOS и CPM, с чем вы наверняка знакомы, программисты работали за продвинутыми (для тех дней) терминалами, используя их для ввода и вывода данных на экран, а для проектирования экрана использовалась бумага в клетку. Каждая клетка была эквивалентна одному символу. Когда же мы имеем дело с программами на Python, запущенными в терминале, у нас по-прежнему остаётся экран 24×80. Однако с этим ограничением можно легко справиться, заранее подготовившись и всё продумав. Так что сходите в ближайший магазин канцтоваров и обзаведитесь парой тетрадей в клетку.

В любом случае, давайте сразу начнём и создадим нашу первую программу с использованием Curses. (Справа сверху). Просмотрите код, а затем я вам всё объясню.

Коротко, но просто. Давайте просмотрим код строчку за строчкой. Первым делом мы импортируем пакеты, с кото-рыми вы уже знакомы. Далее, создаём новый объект Curses, инициализируем и называем его myscreen (myscreen = curses.initscr()). Это — наш «холст», на котором мы будем рисовать. Далее мы используем команду myscreen.border(0), чтобы нарисовать контур вокруг «холста». Это делать не обязательно, но так выглядит посимпатичнее. Добавляем метод addstr для «написания» некоторого текста на поле начиная с 25 позиции 12 строчки. Воспринимайте метод .addstr как функцию print для Curses. Наконец метод .refresh() делает нашу работу видимой. Если не обновить экран, то мы не увидим внесённых изменений. Далее ждём, когда пользователь нажмёт любую клавишу (.getch), и передаём управление экраном системе (.endwin) для продолжения нормальной работы в терминале. Функция curses.endwin() ОЧЕНЬ важна и, если её не вызвать, то терминал останется в большом беспорядке. Так что удостоверьтесь, что вызвали эту функцию в конце вашего приложения.

Сохраните эту программу как «CursesExample1.py» и запустите в терминале. Один нюанс: всякий раз при добавле-нии контура он занимает по одной «доступной» клетке на каждый символ контура. Кроме того, отсчёт позиций линий и символов начинается с НУЛЯ. Это означает, что первая линия границы — это нулевая строчка на экране, а последняя линия — это 23 строчка. Следовательно, крайняя левая верхняя пози-ция — это (0,0), а крайняя пра-вая нижняя — (23,79). Давайте покажем это на примере (cправа сверху). Всё достаточно просто, за исключением блоков try/finally. Помните, я говорил, что curses.endwin() — это ОЧЕНЬ важная функция и её нужно обязательно вызывать в конце кода? Так вот, в данном случае, если дела пойдут очень плохо, будет вызвана функция endwin. Существует множество спосо-бов вызвать эту функцию, но этот мне кажется достаточно простым.

Теперь давайте создадим красивое меню. Если вы припом-ните, мы писали программу «Поваренная книга», у которой было меню (Программа на Python — Часть 8). Когда мы что-то набирали, всё содержимое просто прокручивалось вверх. Сейчас мы, используя вышеска-занное, сделаем модель меню, чтобы приукрасить «Поваренную книгу». Ниже приведён старый кусок кода.

На этот раз мы будем использовать Curses. Начнём со следующего шаблона. Вероятно вам захочется сохранить этот фрагмент (справа снизу) для использования в своих будущих программах.

Сохраните шаблон как «cursesmenu1.py». Теперь мы можем работать с файлом, сохраняя шаблон.

Прежде чем мы пойдём дальше по нашему коду, давайте рассмотрим его по блокам. Здесь на псевдокоде написано то, что мы хотим сделать.

Конечно, этот псевдокод всего лишь… псевдо. Но он даёт нам представление о том, что мы хотим сделать. Поскольку это всего лишь пример, мы дойдём только до этого момента, но вы можете взять шаблон целиком. Давайте начнём с основного цикла (посередине справа).

Кода здесь немного. Мы имеем только блоки try/finally, так же, как они представлены в шаблоне. Инициализируем экран Curses и вызываем функцию LogicLoop. Этот код показан в нижнем правом углу.

И вновь, кода немного, но это только образец. Здесь мы вызываем две функции: DoMainMenu и MainInKey. DoMainMenu (справа) выводит наше меню на экран, а MainInKey управляет всем остальным.

Заметьте, что эта функция не делает ничего кроме очистки экрана (myscreen.erase), и затем выводит на экран то, что нам нужно. Обработкой событий ввода с клавиатуры мы тут не занимаемся. Этим занимается функция MainInKey, которая показана на следующей странице.

Эта функция действительно проста. Цикл while выполняется до тех пор, пока пользователь не нажал клавишу «0» (ноль). Внутри цикла мы сравниваем её с различными значениями. В зависимости от результата выполняются определённые действия и вызывается главное меню. Эти действия вы уже и сами можете добавить в код. Давайте лучше посмотрим на пункт 2, «Search for a Recipe». С самим меню всё понятно, а вот с функцией Inkey2 (справа) посложнее.

Вновь мы используем стан-дартный цикл while. Используем выражение doloop = 1 для обеспечения бесконечного цикла while и команду break для выхода из цикла. Три элемента выбора очень похожи. Главное их отличие в том, что мы начинаем с переменной tmpstr и затем добавляем к ней текст из выбранного пункта, делая меню более дружелюбным. Далее вызываем функцию GetSearchLine для получения текста поиска. Функция getstr используется для получения строки целиком, а не только отдельных символов. Полученную строку мы в дальнейшем используем.

Полный код доступен по адресу: http://pastebin.com/EluZ3T4P

И напоследок, если вы заинтересованы в дальнейшем изучении Curses, то знайте, что кроме использованных в этой статье методов, есть ещё много других. Помимо поиска в Google, наилучшей отправной точкой станет официальная документация на http://docs.python.org/library/curses.html

До скорых встреч.

Коды

#!/usr/bin/env python 
# CursesExample1
#------------------------------- 
# Curses Programming Sample 1 
#------------------------------- 
import curses 
myscreen = curses.initscr() 
myscreen.border(0) 
myscreen.addstr(12, 25, "See Curses, See Curses Run!") 
myscreen.refresh() 
myscreen.getch() 
curses.endwin()

#!/usr/bin/env python 
# CursesExample2
import curses 
#========================================================== 
#                       MAIN LOOP 
#========================================================== 
try: 
    myscreen = curses.initscr() 
    myscreen.clear() 
    myscreen.addstr(0,0,"0        1         2         3         4         5         6         7") 
    myscreen.addstr(1,0,"12345678901234567890123456789012345678901234567890123456789012345678901234567890") 
    myscreen.addstr(10,0,"10") 
    myscreen.addstr(20,0,"20") 
    myscreen.addstr(23,0, "23 - Press Any Key to Continue") 
    myscreen.refresh() 
    myscreen.getch() 
finally: 
    curses.endwin()

#!/usr/bin/env python 
#------------------------------- 
# Curses Programming Template 
#------------------------------- 
import curses 

def InitScreen(Border): 
    if Border == 1: 
        myscreen.border(0) 

#========================================================== 
#                       MAIN LOOP 
#========================================================== 
myscreen = curses.initscr() 
InitScreen(1) 
try: 
    myscreen.refresh() 
    # Your Code Stuff Here... 
    myscreen.addstr(1,1, "Press Any Key to Continue") 
    myscreen.getch() 
finally: 
    curses.endwin()

===================================================
               RECIPE DATABASE
===================================================
 1 - Show All Recipes
 2 - Search for a recipe
 3 - Show a Recipe
 4 - Delete a recipe
 5 - Add a recipe
 6 - Print a recipe
 0 - Exit
===================================================
Enter a selection ->

curses.initscreen
LogicLoop
    ShowMainMenu                     # Show the main menu
    MainInKey                        # This is our main input handling routine
        While Key != 0:
            If Key == 1:
                ShowAllRecipesMenu   # Show the All Recipes Menu
                Inkey1               # Do the input routines for this
                ShowMainMenu         # Show the main menu
            If Key == 2:
                SearchForARecipeMenu # Show the Search for a Recipe Menu
                InKey2               # Do the input routines for this option
                ShowMainMenu         # Show the main menu again
            If Key == 3:
                ShowARecipeMenu      # Show the Show a recipe menu routine
                InKey3               # Do the input routine for this routine
                ShowMainMenu         # Show the main menu again
		…	               # And so on and so on
curses.endwin()                      # Restore the terminal

def DoMainMenu():
    myscreen.erase()
    myscreen.addstr(1,1,  "========================================")
    myscreen.addstr(2,1,  "           Recipe Database")
    myscreen.addstr(3,1,  "========================================")
    myscreen.addstr(4,1,  "  1 - Show All Recipes")
    myscreen.addstr(5,1,  "  2 - Search for a recipe")
    myscreen.addstr(6,1,  "  3 - Show a recipe")
    myscreen.addstr(7,1,  "  4 - Delete a recipe")
    myscreen.addstr(8,1,  "  5 - Add a recipe")
    myscreen.addstr(9,1,  "  6 - Print a recipe")
    myscreen.addstr(10,1, "  0 - Exit")
    myscreen.addstr(11,1, "========================================")
    myscreen.addstr(12,1, "  Enter a selection: ")
    myscreen.refresh()
    
#    MAIN LOOP
try:
    myscreen = curses.initscr()
    LogicLoop()
finally:
    curses.endwin()
    
def LogicLoop():
    DoMainMenu()
    MainInKey()
    
def MainInKey():
    key = 'X'
    while key != ord('0'):
        key = myscreen.getch(12,22)
        myscreen.addch(12,22,key)
        if key == ord('1'):
            ShowAllRecipesMenu()
            DoMainMenu()
        elif key == ord('2'):
            SearchForARecipeMenu()
            InKey2()
            DoMainMenu()
        elif key == ord('3'):
            ShowARecipeMenu()
            DoMainMenu()
        elif key == ord('4'):
            NotReady("'Delete A Recipe'")
            DoMainMenu()
        elif key == ord('5'):
            NotReady("'Add A Recipe'")
            DoMainMenu()
        elif key == ord('6'):
            NotReady("'Print A Recipe'")
            DoMainMenu()
        myscreen.refresh()
        
def SearchForARecipeMenu():
    myscreen.addstr(4,1, "-------------------------------")
    myscreen.addstr(5,1, " Search in")
    myscreen.addstr(6,1, "-------------------------------")
    myscreen.addstr(7,1, " 1 - Recipe Name")
    myscreen.addstr(8,1, " 2 - Recipe Source")
    myscreen.addstr(9,1, " 3 - Ingredients")
    myscreen.addstr(10,1," 0 - Exit")
    myscreen.addstr(11,1,"Enter Search Type -> ")
    myscreen.refresh()

def InKey2():
    key = 'X'
    doloop = 1
    while doloop == 1:
        key = myscreen.getch(11,22)
        myscreen.addch(11,22,key)
        tmpstr = "Enter text to search in "
        if key == ord('1'):
            sstr = "'Recipe Name' for -> "
            tmpstr = tmpstr + sstr
            retstring = GetSearchLine(13,1,tmpstr)
            break
        elif key == ord('2'):
            sstr = "'Recipe Source' for -> "
            tmpstr = tmpstr + sstr
            retstring = GetSearchLine(13,1,tmpstr)
            break
        elif key == ord('3'):
            sstr = "'Ingredients' for -> "
            tmpstr = tmpstr + sstr
            retstring = GetSearchLine(13,1,tmpstr)
            break
        else:
            retstring = ""
            break
    if retstring != "":
        myscreen.addstr(15,1,"You entered - " + retstring)
    else:
        myscreen.addstr(15,1,"You entered a blank string")
    myscreen.refresh()
    myscreen.addstr(20,1,"Press a key")
    myscreen.getch()

def GetSearchLine(row,col,strng):
    myscreen.addstr(row,col,strng)
    myscreen.refresh()
    instring = myscreen.getstr(row,len(strng)+1)
    myscreen.addstr(row,len(strng)+1,instring)
    myscreen.refresh()
    return instring