Hlavní navigace

Tvorba textového rozhraní s knihovnou prompt_toolkit: základní prvky TUI

Pavel Tišnovský

Ve třetím článku o knihovně prompt_toolkit se seznámíme s formátováním textu, volitelně i se zvýrazněním syntaxe. Posléze si popíšeme i všechny standardní dialogy, které mohou tvořit součást textového rozhraní aplikací.

Doba čtení: 25 minut

11. Změna popisu tlačítek v dialogu

12. Dialog, v němž je počet a popis tlačítek plně konfigurovatelný

13. Dialog určený pro vstup textu nebo dalších údajů

14. Režim zadávání hesla popř. dalších údajů, které se nemají přímo zobrazit na terminálu

15. Dialog se sadou přepínacích tlačítek (radio buttons)

16. Dialog zobrazující průběh výpočtu („teploměr“)

17. Nastavení stylu zobrazení dialogů

18. Tvorba aplikací s plnohodnotným textovým uživatelským rozhraním

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Tvorba textového uživatelského rozhraní s knihovnou prompt_toolkit: základní prvky TUI

Na předchozí dva články [1] [2], v nichž jsme se seznámili s některými možnostmi nabízenými knihovnami GNU Readline a prompt_toolkit dnes navážeme. Prozatím jsme se totiž dozvěděli „pouze“ to, jakým způsobem je možné zajistit vstup dat popř. příkazů s využitím v mnoha oblastech vylepšeného příkazového řádku (historie příkazů, automatické doplňování, validace, barevné zvýraznění atd.). Ovšem pro aplikace s plnohodnotným textovým uživatelským rozhraním (TUI – Text User Interface) nemusí být příkazový řádek dostačující a je nutné použít i další ovládací prvky (widgety). Z tohoto důvodu se nejprve seznámíme s možností výpisu formátovaných a obarvených textů a následně pak se standardními dialogy, které programátorům knihovna prompt_toolkit nabízí a které lze velmi jednoduše použít (podobně jako v BASHi knihovnu/nástroj Dialog nebo zenity). Poté se již budeme věnovat aplikacím s plnohodnotným textovým uživatelským rozhraním, konfigurovatelnými widgety ovládanými klávesnicí i myší apod.

Obrázek 1: Příklad podrobněji vysvětlený v předchozím článku, v němž jsme si popsali zdánlivě triviální funkci pro vstup textových údajů.

Poznámka: první část dnešního článku sice nemusí být příliš záživná, ovšem formátovaný text (založený například na dále zmíněné třídě HTML) bude možné využít například ve všech standardních dialozích, popř. i ve widgetech, s nimiž se postupně seznámíme.

2. Výpis formátovaného textu na konzoli

První funkcí, se kterou se v dnešním článku seznámíme, je funkce nazvaná print_formatted_text(). Tato funkce je navržena takovým způsobem, aby byla zpětně kompatibilní se standardní funkcí print() z knihovny Pythonu, což mj. znamená, že lze volit způsob odřádkování apod. Tato funkce plně podporuje Unicode, což si ostatně můžeme ukázat na příkladu, který po svém spuštění stáhne známý soubor pojmenovaný „UTF-8-demo.txt“ a následně ho řádek po řádku vypíše na terminál, a to právě s využitím funkce print_formatted_text:

from prompt_toolkit import print_formatted_text
from urllib.request import urlopen
 
input = urlopen("http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-demo.txt")
 
for line in input:
    line = line.strip().decode("utf-8")
    print_formatted_text(line)

Způsob zobrazení výsledků do značné míry závisí na nastavení terminálu, použitém fontu atd.:

Obrázek 2: První obrazovka s obsahem souboru „UTF-8-demo.txt“. Tento screenshot i všechny následující screenshoty jsou vytvořeny v Xtermu s podporou Unicode.

Obrázek 3: Poslední část obsahu souboru „UTF-8-demo.txt“.

Proč je však výhodnější použít funkci print_formatted_text() namísto klasické funkce print()? Důvodů je více, ovšem ten hlavní spočívá v tom, že této funkci lze skutečně předat text s formátovacími značkami. Takový text je představován instancemi tříd FormattedText, HTML nebo ANSI. Prozatím se zaměříme především na použití třídy HTML.

3. Použití třídy HTML a základních HTML značek <b>, <i> a <u>

Třída HTML, která je taktéž součástí knihovny prompt_toolkit, se používá pro reprezentaci formátovaného textu, v němž jsou využity značky odvozené od značek, které nalezneme v jazyku HTML. Ve skutečnosti je rozpoznáno jen minimum HTML značek, ovšem – což je zajímavé – je možné v případě potřeby použít i takzvané pseudoznačky, popř. si dokonce nadefinovat značky vlastní. Nesmíme si však představovat, že kombinací třídy HTML a funkce print_formatted_text() získáme obdobu webového prohlížeče pracujícího v textovém režimu (w3m, lynx, links, …). Třída HTML totiž ve skutečnosti slouží pouze pro popis zobrazení textu – stylu, barvy textu, barvy pozadí. Styl je možné volit pomocí značek <b>, <i> a <u> a jejich vzájemných kombinací. Konkrétní způsob zobrazení je plně závislý na možnostech terminálu. Podívejme se na příklad, v němž funkci print_formatted_text() předáme instanci třídy HTML:

from prompt_toolkit import print_formatted_text, HTML
 
print_formatted_text(HTML('zpráva obsahující <b>tučný text</b>'))
print_formatted_text(HTML('zpráva s <i>textem tištěným kurzivou</i>'))
print_formatted_text(HTML('text obsahující <u>tato podtržená slova</u>'))
print_formatted_text(HTML('test kombinace <b><i>tučné kurzivy</i></b>'))
print_formatted_text(HTML('test kombinace <b><u>tučného podtrženého textu</u></b>'))
print_formatted_text(HTML('test kombinace <i><u>podtrženého textu psaného kurzivou</u></i>'))

Obrázek 4: Dnešní druhý demonstrační příklad po spuštění v xtermu.

Poznámka: povšimněte si, že se v mém případě (při použití xtermu) kurziva zobrazila stejně jako běžný text. Toto chování lze změnit v konfiguraci terminálu.

4. Specifikace barvy textu: použití značky <style> popř. nových pseudoznaček

Kromě stylu textu (běžný text, kurziva, tučný text, podtržení) si můžeme vyzkoušet změnit barvu textu. Pro tento účel slouží především značka <style> s atributem fg. Barvy je možné vybírat ze dvou palet – první paleta obsahuje osm základních barev, každou se dvěma intenzitami (světlejší, tmavší), druhá paleta pak 256 barev. Záleží ovšem na konkrétní konfiguraci terminálu, jestli například barva „red“ ze standardní palety bude skutečně zobrazena červeně, protože prakticky všechny moderní emulátory terminálu nabízí uživatelské definice použité barvové palety. Podívejme se nyní na způsob zobrazení textu s použitím 16barevné standardní palety (osm barev, každá se dvěma intenzitami). Tento příklad po svém spuštění nejdříve vypíše zprávy pomocí standardní funkce print() a následně s využitím funkce print_formatted_text():

from prompt_toolkit import print_formatted_text, HTML
from prompt_toolkit.output.vt100 import FG_ANSI_COLORS
 
for color in sorted(FG_ANSI_COLORS):
    message = "<style fg='{color}'>zpráva vypsaná barvou {color}</style>".format(color=color)
    print(message)
 
print("\n\n")
 
for color in sorted(FG_ANSI_COLORS):
    message = "<style fg='{color}'>zpráva vypsaná barvou {color}</style>".format(color=color)
    print_formatted_text(HTML(message))
Poznámka: zpráv se vždy vytiskne sedmnáct a ne šestnáct, a to kvůli pseudobarvě ansidefault.

Obrázek 5: Prvních sedmnáct zpráv vypsaných funkcí print().

Obrázek 6: Zprávy vypsané funkcí print_formatted_text().

Ve standardní šestnáctibarevné paletě jsou k dispozici tyto barvy (měly by být využitelné prakticky ve všech emulátorech terminálu, dokonce i v terminálu ve Windows):

Jméno standardní barvy
ansiblack
ansired
ansigreen
ansiyellow
ansiblue
ansimagenta
ansicyan
ansigray
 
ansibrightblack
ansibrightred
ansibrightgreen
ansibrightyellow
ansibrightblue
ansibrightmagenta
ansibrightcyan
ansiwhite

Kromě standardní značky <style> s atributem fg můžeme použít i takzvané pseudoznačky, jejichž jména přímo odpovídají názvu barvy. Jedná se například o pseudoznačku <ansired>. Opět se podívejme na zdrojový kód příkladu, který tyto pseudoznačky postupně používá při tisku zpráv na terminál:

from prompt_toolkit import print_formatted_text, HTML
from prompt_toolkit.output.vt100 import FG_ANSI_COLORS
 
for color in sorted(FG_ANSI_COLORS):
    message = "<{color}>zpráva vypsaná barvou {color}</{color}>".format(color=color)
    print(message)
 
print("\n\n")
 
for color in sorted(FG_ANSI_COLORS):
    message = "<{color}>zpráva vypsaná barvou {color}</{color}>".format(color=color)
    print_formatted_text(HTML(message))

Obrázek 7: Příklad použití pseudoznaček s názvem jednotlivých barev.

5. Změna barvy pozadí textu; kombinace barvy popředí, pozadí i stylu výpisu

Kromě samotné barvy textu je možné na všech moderních emulátorech terminálu měnit i barvu pozadí. V závislosti na možnostech terminálu je k dispozici buď osm barev, šestnáct barev (přesněji osm barev ve dvou intenzitách) nebo 256 barev (16 základních, kombinace 6×6×6=216 odstínů každé barvové složky a 20 odstínů šedé). Opět si ukažme použití základních šestnácti barev. Ty mají jména:

Jméno standardní barvy
ansiblack
ansired
ansigreen
ansiyellow
ansiblue
ansimagenta
ansicyan
ansigray
 
ansibrightblack
ansibrightred
ansibrightgreen
ansibrightyellow
ansibrightblue
ansibrightmagenta
ansibrightcyan
ansiwhite

Následující demonstrační příklad se na terminál pokusí vykreslit všech 16×16=256 kombinací popředí a pozadí:

from prompt_toolkit import print_formatted_text, HTML
from prompt_toolkit.output.vt100 import FG_ANSI_COLORS, BG_ANSI_COLORS
 
for bg_color in sorted(BG_ANSI_COLORS):
    for fg_color in sorted(FG_ANSI_COLORS):
        message = "<p fg='{fg_color}' bg='{bg_color}'> test </p>".format(
            fg_color=fg_color, bg_color=bg_color)
        print_formatted_text(HTML(message), end="")
 
    print()

Obrázek 8: Kombinace 16×16 barev popředí a pozadí.

V dalším příkladu zkombinujeme barvy popředí a pozadí s různými styly textu, tj. normálním textem, podtrženým textem, kurzivou a tučným textem:

from prompt_toolkit import print_formatted_text, HTML
from prompt_toolkit.output.vt100 import FG_ANSI_COLORS, BG_ANSI_COLORS
 
for bg_color in sorted(BG_ANSI_COLORS):
    for fg_color in sorted(FG_ANSI_COLORS):
        message = "<p fg='{fg_color}' bg='{bg_color}'>XX <u>XX</u> <i>XX</i> <b>XX</b>   </p>".format(
            fg_color=fg_color, bg_color=bg_color)
        print_formatted_text(HTML(message), end="")
 
    print()

Obrázek 9: Kombinace 16×16 barev popředí a pozadí se čtyřmi styly textu: normální, podtržený, kurziva a tučný.

6. Obarvení výstupu s využitím lexeru z knihovny Pygments

O použití knihovny Pygments jsme se již krátce zmínili v předchozím článku v souvislosti s obarvením textu zadávaného uživatelem. To je užitečná vlastnost, která je využitelná například při tvorbě interaktivních konzolí různých (doménově specifických) jazyků a nástrojů. Podobně užitečná může být i další možnost – obarvit výstup s využitím lexeru naimportovaného z knihovny Pygments. Takzvaný syntax highlighting (což je někdy poněkud nepřesné pojmenování) při obarvení zdrojových kódů napsaných přímo v Pythonu vypadá následovně:

from pygments import lex
from pygments.token import Token
from pygments.lexers import PythonLexer
 
from prompt_toolkit.formatted_text import PygmentsTokens
from prompt_toolkit import print_formatted_text
 
code = """
for i in range(1, 10):
    print(i)
    if i > 5:
        break
    do_something(i)
"""
 
tokens = list(lex(code, lexer=PythonLexer()))
print_formatted_text(PygmentsTokens(tokens))

Obrázek 10: Obarvení textu na základě lexeru vytvořeného pro programovací jazyk Python.

Jen pro zajímavost si ukažme i některé další možnosti syntax highlightingu nabízeného knihovnou Pygments. Tento příklad zobrazuje zdrojový kód naprogramovaný ve starobylém BASICu:

from pygments import lex
from pygments.token import Token
from pygments.lexers.basic import CbmBasicV2Lexer
 
from prompt_toolkit.formatted_text import PygmentsTokens
from prompt_toolkit import print_formatted_text
 
code = """
10 FOR I=0 TO 63
20 FOR J=43 TO 0 STEP -1
30 LET CX=(I-52)/31
40 LET CY=(J-22)/31
50 LET ZX=0
60 LET ZY=0
70 LET ITER=0
80 LET ZX2=ZX*ZX
85 LET ZY2=ZY*ZY
90 LET ZY=2*ZX*ZY+CY
100 LET ZX=ZX2-ZY2+CX
110 LET ITER=ITER+1
120 IF ZX2+ZY2<=4 AND ITER<200 THEN GOTO 80
130 IF ITER=200 THEN PLOT I, J
140 NEXT J
150 NEXT I
"""
 
tokens = list(lex(code, lexer=CbmBasicV2Lexer()))
print_formatted_text(PygmentsTokens(tokens))

Obrázek 11: Obarvení textu na základě lexeru vytvořeného pro programovací jazyk BASIC (zde konkrétně Commodore BASIC).

7. Konstrukce výstupu se specifikací typů jednotlivých tokenů

Existuje ještě jedna potenciálně užitečná varianta obarvení (zdrojových) kódů vytištěných na terminál. Tato varianta spočívá v tom, že se text, který se má zobrazit s obarvením, rozdělí na takzvané lexikální tokeny, přičemž každému tokenu je přiřazen jeho význam (typ, jméno). Příkladem mohou být klíčová slova, tj. krátké texty uložené v tokenu se jménem Token.Keyword. V případě, že jsou data, která se mají zobrazit na výstupu, skutečně reprezentována formou tokenů, může být jejich vytištění poměrně snadné, což je ostatně patrné při pohledu na další příklad, ve kterém je formou seznamu specifikováno několik tokenů tvořících jednoduchý program (odpovídající Pythonu, ale i mnoha dalším programovacím jazykům). Povšimněte si, že každý token je skutečně reprezentován svým jménem/typem a konkrétním textem):

from pygments.token import Token
from prompt_toolkit import print_formatted_text
from prompt_toolkit.formatted_text import PygmentsTokens
 
text = [
    (Token.Keyword, 'print'),
    (Token.Punctuation, '('),
    (Token.Literal.String.Double, '"'),
    (Token.Literal.String, 'hello'),
    (Token.Literal.String.Double, '"'),
    (Token.Punctuation, ','),
    (Token.Text.Whitespace, ' '),
    (Token.Literal.String.Single, '"'),
    (Token.Literal.String, 'world'),
    (Token.Literal.String.Single, '"'),
    (Token.Punctuation, ')'),
    (Token.Text, '\n'),
]
 
print_formatted_text(PygmentsTokens(text))

Obrázek 12: Obarvení textu složeného postupně z jednotlivých tokenů.

Následuje výpis jednotlivých typů tokenů, které lze použít. S touto technologií se budeme zabývat v samostatném článku o knihovně Pygments:

Skupina Jméno/typ tokenu
Keyword Keyword.Constant
  Keyword.Declaration
  Keyword.Namespace
  Keyword.Pseudo
  Keyword.Reserved
  Keyword.Type
   
Number Number.Bin
  Number.Float
  Number.Hex
  Number.Integer
  Number.Integer.Long
  Number.Oct
   
Name Name.Attribute
  Name.Builtin
  Name.Builtin.Pseudo
  Name.Class
  Name.Constant
  Name.Decorator
  Name.Entity
  Name.Exception
  Name.Function
  Name.Function.Magic
  Name.Label
  Name.Namespace
  Name.Other
  Name.Tag
  Name.Variable
  Name.Variable.Class
  Name.Variable.Global
  Name.Variable.Instance
  Name.Variable.Magic
   
Literal Literal.Date
   
String String.Affix
  String.Backtick
  String.Char
  String.Delimiter
  String.Doc
  String.Double
  String.Escape
  String.Heredoc
  String.Interpol
  String.Other
  String.Regex
  String.Single
  String.Symbol
   
Comment Comment.Hashbang
  Comment.Multiline
  Comment.Preproc
  Comment.Single
  Comment.Special
   
Operator Operator.Word
   
Punctuation Punctuation
   
Generic Generic.Deleted
  Generic.Emph
  Generic.Error
  Generic.Heading
  Generic.Inserted
  Generic.Output
  Generic.Prompt
  Generic.Strong
  Generic.Subheading
  Generic.Traceback
Poznámka: mapování mezi typem tokenu a barvou či stylem textu je samozřejmě konfigurovatelné.

8. Dialogové boxy nabízené knihovnou prompt_toolkit

S dialogovými okny jsme se na stránkách Rootu setkali, například již při popisu možností knihovny Tkinter. Připomeňme si, že kromě zvládnutí vyšší komplexnosti aplikací hrají dialogová okna i další roli – pomáhají totiž standardizovat některé společné části aplikací. Například pro otevření souboru, uložení souboru, tisk dokumentu nebo výběr barvy je možné (a velmi vhodné) použít standardní dialog dodávaný s GUI či TUI systémem. Do jaké míry se tento systém standardizace využívá, čtenář patrně vidí na svém desktopu sám: určitá míra standardizace je patrná, také je však zřejmé, že mnohé aplikace využívají jiné GUI knihovny, o míchání několika desktopových prostředích ani nemluvě (to zdaleka není pouze problém GNU softwaru, „lidová tvořivost“ je vidět i na komerčních programech).

Se standardními dialogy se setkáme i v knihovně prompt_toolkit, v níž se až na jednu výjimku používají velmi jednoduše – pouze se zavolá příslušná funkce pro zobrazení dialogu a poté aplikace čeká, až uživatel zadá vstupní data popř. až zvolí nějaké tlačítko. Tyto dialogy se ovládají velmi podobně jako nástroje Zenity nebo Dialog, které lze volat z příkazového řádku. V prompt_toolkitu existuje vlastně jen jediný dialog s odlišným chováním: jedná se o dialog určený pro zobrazení probíhající činnosti, který obsahuje jak klasický progress bar, tak i plochu, do které může aplikace v průběhu výpočtů vypisovat ladicí informace.

Poznámka: všechny dialogy je možné ovládat i s využitím myši.

9. Dialog pro zobrazení zprávy uživateli – message_dialog

Nejjednodušší dialog nabízený knihovnou prompt_toolkit se jmenuje message_dialog a jak již jeho jméno napovídá, slouží k zobrazení zprávy uživateli. Samotný dialog může obsahovat titulek, tlačítko pro jeho zavření a zprávu, která může být v případě potřeby víceřádková:

from prompt_toolkit.shortcuts import message_dialog
 
message_dialog(
    title='Software failure',
    text='Guru Meditation #12345678.ABCDEFFF\nPress ENTER to continue.')

Obrázek 13: Zpráva zobrazená uživateli pomocí dialogu message_dialog.

Tento dialog obsahuje i nepovinný parametr ok_text, kterým je možné specifikovat text zobrazený na (jediném) tlačítku. Opět se podívejme na způsob použití:

from prompt_toolkit.shortcuts import message_dialog
 
message_dialog(
    title='Software failure',
    text='Guru Meditation #12345678.ABCDEFFF\nPress ENTER to continue.',
    ok_text='[Enter]')

Obrázek 14: Změna textu na (jediném) tlačítku zobrazeném na dialogu.

10. Základní dialog pro výběr odpovědi typu Ano/Ne

Druhý typ dialogu je již nepatrně složitější, neboť uživateli nabízí volbu typu Ano/Ne. Tento dialog se zobrazí zavoláním funkce yes_no_dialog, což je ukázáno v dalším příkladu:

from prompt_toolkit.shortcuts import message_dialog, yes_no_dialog
 
response = yes_no_dialog(
    title='Software failure',
    text='Guru Meditation #12345678.ABCDEFFF\nRestart system?')
 
message_dialog(
    title='Your choice',
    text='Yes' if response else 'No')

Tento dialog v případě kladné odpovědi vrátí hodnotu True; v opačném případě False.

Obrázek 15: Dialog pro výběr odpovědi typu Ano/Ne, který je představován funkcí yes_no_dialog.

11. Změna popisu tlačítek v dialogu

Podobně jako u dialogu message_dialog je možné i v případě dialogu typu yes_no_dialog zvolit popisky jednotlivých tlačítek. Změna je jednoduchá – postačuje použít nepovinné parametry pojmenované yes_text a no_text:

from prompt_toolkit.shortcuts import message_dialog, yes_no_dialog
 
response = yes_no_dialog(
    title='Tento program provedl neplatnou operaci',
    text='Nevíme, co se stalo, známe jen kód chyby:\n#12345678.ABCDEFFF\n\nProvést restart?',
    yes_text='Ano',
    no_text='Ne')
 
message_dialog(
    title='Zadali jste volbu',
    text='Ano' if response else 'Ne')

Obrázek 16: Dialog pro výběr odpovědi typu Ano/Ne, který je představován funkcí yes_no_dialog, tentokrát plně počeštěný.

12. Dialog, v němž je počet a popis tlačítek plně konfigurovatelný

Třetí dialog je představován funkcí pojmenovanou button_dialog a slouží pro zobrazení libovolného počtu tlačítek. Informace o zobrazených tlačítkách se předává v parametru buttons a musí se jednat o seznam obsahující dvojice popisek:hodnota-vrácená-dialogem, například:

buttons=[
    ('Abort', 'abort'),
    ('Retry', 'retry'),
    ('Fail', 'fail')])
Poznámka: pokud potřebujete provést výběr z více než cca šesti hodnot, je výhodnější použít radiolist_dialog.

Opět se podívejme na jednoduchý příklad:

from prompt_toolkit.shortcuts import message_dialog, button_dialog
 
response = button_dialog(
    title='Tento program provedl neplatnou operaci',
    text='Not ready reading drive A',
    buttons=[
        ('Abort', 'abort'),
        ('Retry', 'retry'),
        ('Fail', 'fail')])
 
message_dialog(
    title='Zadali jste volbu',
    text=response)

Obrázek 17: Dialog typu button_dialog se třemi specifikovanými tlačítky.

Alternativně je možné použít výčet s hodnotami, které se mají vrátit po uzavření dialogu libovolným tlačítkem. Kód se změní následovně:

from enum import Enum
from prompt_toolkit.shortcuts import message_dialog, button_dialog
 
Response = Enum('Response', 'abort retry fail')
 
response = button_dialog(
    title='Tento program provedl neplatnou operaci',
    text='Not ready reading drive A',
    buttons=[
        ('Abort', Response.abort),
        ('Retry', Response.retry),
        ('Fail',  Response.fail)])
 
message_dialog(
    title='Zadali jste volbu',
    text=str(response))

Obrázek 18: Opět dialog typu button_dialog se třemi specifikovanými tlačítky.

13. Dialog určený pro vstup textu nebo dalších údajů

Pro vstup textu slouží funkce nazvaná input_dialog, která zobrazí dialog se vstupním textovým polem. V tomto poli je možné používat některé klávesové zkratky, s nimiž jsme se již seznámili (například Ctrl+A, Ctrl+E, Ctrl+W apod.). Nicméně na tomto místě je vhodné poznamenat, že možnosti funkce prompt() jsou větší než u funkce input_dialog():

from prompt_toolkit.shortcuts import message_dialog, input_dialog
 
response = input_dialog(
    title='Zadání uživatelského jména',
    text='Uživatelské jméno:')
 
if response is not None:
    message_dialog(
        title='Zadání uživatelského jména',
        text='zadali jste: {name}'.format(name=response if response else 'nic :)'))
else:
    message_dialog(
        title='Zadání uživatelského jména',
        text='Jméno nebylo zadáno')

Obrázek 19: Zadání uživatelského jména v dialogu typu input_dialog.

14. Režim zadávání hesla popř. dalších údajů, které se nemají přímo zobrazit na terminálu

Při vstupu některých tajných údajů je možné zvolit režim zadávání hesla, a to pomocí nepovinného parametru pojmenovaného password. Úprava předchozího příkladu je snadná:

from prompt_toolkit.shortcuts import message_dialog, input_dialog
 
response = input_dialog(
    title='Zadání hesla',
    text='Heslo:',
    password=True)
 
if response is not None:
    message_dialog(
        title='Zadání hesla',
        text='zadali jste: {password}'.format(password=response if response else 'nic :)'))
else:
    message_dialog(
        title='Zadání hesla',
        text='Heslo nebylo zadáno')

Obrázek 20: Vstupní dialog typu input_dialog v režimu zadávání hesla.

15. Dialog se sadou přepínacích tlačítek (radio buttons)

Dialog pojmenovaný radiolist_dialog (pozor: ne radio_list_dialog jak je uvedeno v dokumentaci!) slouží pro výběr prvku z obecně většího množství hodnot zobrazených formou přepínacích tlačítek a popř. i scrollovacího pruhu. Hodnoty, z nichž se přepínací tlačítka mají vytvořit, jsou opět představovány seznamem dvojic, ovšem nyní jsou z nějakého důvodu přehozeny hodnoty s popiskami. Výběr jedné ze čtyř dostupných hodnot zajistí tento příklad:

from prompt_toolkit.shortcuts import message_dialog, radiolist_dialog
 
response = radiolist_dialog(
    title='Zadání příkazu',
    text='Zadejte příkaz (quit, exit, help, eval):',
    values=[
        ('quit', 'Quit'),
        ('exit', 'Exit'),
        ('help', 'Help'),
        ('eval', 'Eval')])
 
if response is not None:
    message_dialog(
        title='Zadání příkazu',
        text='zadali jste: {command}'.format(command=response if response else 'nic :)'))
else:
    message_dialog(
        title='Zadání uživatelského jména',
        text='Příkaz nebyl zadán')

Obrázek 21: Dialog se čtyřmi přepínacími tlačítky a (zde nevyužitým) scrollovacím pruhem na pravé straně.

16. Dialog zobrazující průběh výpočtu („teploměr“)

Poslední typ dialogu, který se jmenuje progress_dialog, se svým chováním odlišuje od všech ostatních dialogů, protože do větší míry komunikuje s kódem aplikace. Základem je specifikace callback funkce, kterou si dialog sám zavolá po svém zobrazení:

response = progress_dialog(
    title='Výpočet',
    text='Probíhá výpočet, prosím čekejte',
    run_callback=simple_callback)

Tato callback funkce musí akceptovat dva parametry, což jsou reference na dvě další pomocné funkce. První z těchto funkcí je možné zavolat pro změnu pozice progress baru („teploměru“) ukazujícího průběh výpočtu a druhou funkci pro zobrazení libovolné zprávy do textového pole. Průběh se udává v procentech, tj. mělo by se jednat o hodnotu v rozsahu 0..100:

def simple_callback(set_percentage_function, log_text_function):
    for counter in range(0, 101, 5):
        log_text_function("Pocitam: {counter}\n".format(counter=counter))
        set_percentage_function(counter)
        sleep(0.5)
    sleep(2)

Obrázek 22: Průběh simulovaného výpočtu.

Jakmile uživatelem definovaná callback funkce skončí, zmizí z okna terminálu i příslušný dialog:

from time import sleep
from prompt_toolkit.shortcuts import message_dialog, progress_dialog
 
 
def simple_callback(set_percentage_function, log_text_function):
    for counter in range(0, 101, 5):
        log_text_function("Pocitam: {counter}\n".format(counter=counter))
        set_percentage_function(counter)
        sleep(0.5)
    sleep(2)
 
 
response = progress_dialog(
    title='Výpočet',
    text='Probíhá výpočet, prosím čekejte',
    run_callback=simple_callback)

Obrázek 23: Průběh simulovaného výpočtu.

17. Nastavení stylu zobrazení dialogů

Podobně, jako bylo možné změnit styl zobrazení příkazového řádku a dalších pomocných prvků textového uživatelského rozhraní, je možné změnit i styl zobrazení dialogů. V následujícím příkladu jsou použity dvě sady pravidel; první je použito pro zobrazení dialogu typu button_dialog, druhé pak pro dialog typu message_dialog. Způsob deklarace stylů jsme si již popsali minule, takže nejužitečnější informací budou jména tříd reprezentujících jednotlivé části TUI:

from enum import Enum
from prompt_toolkit.shortcuts import message_dialog, button_dialog
from prompt_toolkit.styles import Style
 
dialog_stylesheet_1 = Style.from_dict({
    'dialog':             'bg:yellow',
    'dialog frame-label': 'bg:white black',
    'dialog.body':        'bg:#000000 #00ff00',
    'dialog shadow':      'bg:#00aa00',
    })
 
Response = Enum('Response', 'abort retry fail')
 
response = button_dialog(
    title='Tento program provedl neplatnou operaci',
    text='Not ready reading drive A',
    buttons=[
        ('Abort', Response.abort),
        ('Retry', Response.retry),
        ('Fail',  Response.fail)],
    style=dialog_stylesheet_1)
 
 
dialog_stylesheet_2 = Style.from_dict({
    'dialog':             'bg:black',
    'dialog frame-label': 'bg:white black',
    })
 
message_dialog(
    title='Zadali jste volbu',
    text=str(response),
    style=dialog_stylesheet_2)

Obrázek 24: Změna stylu zobrazení dialogu.

V posledním příkladu je ukázáno, že i při zápisu zpráv popř. titulků dialogu lze použít třídu HTML pro formátování a určení barvy textu, barvy pozadí, popř. stylu textu:

from enum import Enum
from prompt_toolkit import HTML
from prompt_toolkit.shortcuts import message_dialog, button_dialog
from prompt_toolkit.styles import Style
 
dialog_stylesheet_1 = Style.from_dict({
    'dialog':             'bg:yellow',
    'dialog frame-label': 'bg:white black',
    'dialog.body':        'bg:#000000 #00ff00',
    'dialog shadow':      'bg:#00aa00',
    })
 
Response = Enum('Response', 'abort retry fail')
 
response = button_dialog(
    title=HTML('Tento program provedl <white>neplatnou</white> operaci'),
    text=HTML('Not <u>ready</u> reading drive <b>A</b>'),
    buttons=[
        ('Abort', Response.abort),
        ('Retry', Response.retry),
        ('Fail',  Response.fail)],
    style=dialog_stylesheet_1)
 
 
dialog_stylesheet_2 = Style.from_dict({
    'dialog':             'bg:black',
    'dialog frame-label': 'bg:white black',
    })
 
message_dialog(
    title='Zadali jste volbu',
    text=HTML("<red>Příkaz:</red> <blue>{response}</blue>".format(response=response)),
    style=dialog_stylesheet_2)

Obrázek 25: Změna stylu dialogu typu button_dialog.

Obrázek 26: Změna stylu dialogu typu message_dialog.

MIF18 tip v článku témata

18. Tvorba aplikací s plnohodnotným textovým uživatelským rozhraním

Technologie, s nimiž jsme se postupně seznámili v předchozích kapitolách, samozřejmě nestačí pro tvorbu aplikací s plnohodnotným (celoobrazovkovým) textovým uživatelským rozhraním (TUI). Pro tento účel nabízí knihovna prompt_toolkit další třídy, s nimiž je například možné implementovat klasickou smyčku událostí apod. S těmito třídami i se způsobem jejich využití se seznámíme v navazující části tohoto miniseriálu.

19. Repositář s demonstračními příklady

Všechny dnes popisované demonstrační příklady byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/pre­sentations. Příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:

Práce s formátovaným textem

# Příklad Popis Odkaz
1 print1_unicode_support.py výpis textu s Unicode znaky https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/print/print1_u­nicode_support.py
2 print2_basic_html_tags.py použití základních HTML značek https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/print/print2_ba­sic_html_tags.py
3 print3_html_styles.py použití HTML stylů při formátování https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/print/print3_html_sty­les.py
4 print4_html_fake_tags.py pseudoznačky HTML při formátování https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/print/print4_html_fa­ke_tags.py
5 print5_html_background_colors.py specifikace barvy pozadí https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/print/print5_html_bac­kground_colors.py
6 print6_combinations.py kombinace barev a HTML stylů https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/print/print6_com­binations.py
7 print7_print_using_lexer.py použití lexeru při obarvování syntaxe (Python) https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/print/print7_prin­t_using_lexer.py
8 print8_print_using_lexer_B.py použití lexeru při obarvování syntaxe (BASIC) https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/print/print8_prin­t_using_lexer_B.py
9 print9_use_pygments.py využití možností knihovny Pygments při obarvování https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/print/print9_u­se_pygments.py

Standardní dialogy

# Příklad Popis Odkaz
1 dialog1_basic_message_dialog.py základní dialog pro zobrazení zprávy https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/dialogs/dialog1_ba­sic_message_dialog.py
2 dialog2_basic_message_dialog_button.py změna textů tlačítek v dialogu https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/dialogs/dialog2_ba­sic_message_dialog_button­.py
3 dialog3_yes_no_dialog.py dialog s tlačítky Yes/No https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/dialogs/dialog3_y­es_no_dialog.py
4 dialog4_custom_yes_no_dialog.py změna textů tlačítek v dialogu Yes/No https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/dialogs/dialog4_cus­tom_yes_no_dialog.py
5 dialog5_button_dialog.py dialog s definovanými tlačítky https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/dialogs/dialog5_but­ton_dialog.py
6 dialog6_button_dialog_enum.py vylepšení předchozího příkladu https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/dialogs/dialog6_but­ton_dialog_enum.py
7 dialog7_basic_input_dialog.py dialog pro vstup textu https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/dialogs/dialog7_ba­sic_input_dialog.py
8 dialog8_password_mode.py režim zápisu hesla https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/dialogs/dialog8_pas­sword_mode.py
9 dialog9_radio_list_dialog.py dialog s přepínacími tlačítky https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/dialogs/dialog9_ra­dio_list_dialog.py
10 dialog10_progress_dialog.py dialog se zobrazením průběhu výpočtu https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/dialogs/dialog10_pro­gress_dialog.py
11 dialog11_dialog_style.py styly ovlivňující zobrazení dialogu https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/dialogs/dialog11_di­alog_style.py
12 dialog12_dialog_html_usage.py použití třídy HTML v dialogu https://github.com/tisnik/pre­sentations/blob/master/prom­pt_toolkit/dialogs/dialog12_di­alog_html_usage.py

20. Odkazy na Internetu

  1. UTF-8 encoded sample plain-text file
    http://www.cl.cam.ac.uk/~mgk25/uc­s/examples/UTF-8-demo.txt
  2. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  3. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  4. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  5. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  6. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  7. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  8. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  9. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  10. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  11. Editline Library (libedit)
    http://thrysoee.dk/editline/
  12. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  13. libedit or editline
    http://www.cs.utah.edu/~bi­gler/code/libedit.html
  14. WinEditLine
    http://mingweditline.sourceforge.net/
  15. rlcompleter — Completion function for GNU readline
    https://docs.python.org/3/li­brary/rlcompleter.html
  16. rlwrap na GitHubu
    https://github.com/hanslub42/rlwrap
  17. rlwrap(1) – Linux man page
    https://linux.die.net/man/1/rlwrap
  18. readline(3) – Linux man page
    https://linux.die.net/man/3/readline
  19. history(3) – Linux man page
    https://linux.die.net/man/3/history
  20. vi(1) – Linux man page
    https://linux.die.net/man/1/vi
  21. emacs(1) – Linux man page
    https://linux.die.net/man/1/emacs
  22. Pygments – Python syntax highlighter
    http://pygments.org/
  23. Write your own lexer
    http://pygments.org/docs/le­xerdevelopment/
  24. Jazyky podporované knihovnou Pygments
    http://pygments.org/languages/
  25. Pygments FAQ
    http://pygments.org/faq/
  26. TUI – Text User Interface
    https://en.wikipedia.org/wiki/Text-based_user_interface
  27. PuDB: výkonný debugger pro Python s retro uživatelským rozhraním (nástroj s plnohodnotným TUI)
    https://www.root.cz/clanky/pudb-vykonny-debugger-pro-python-s-retro-uzivatelskym-rozhranim/
  28. Historie vývoje textových editorů: krkolomná cesta k moderním textovým procesorům
    https://www.root.cz/clanky/historie-vyvoje-textovych-editoru-krkolomna-cesta-k-modernim-textovym-procesorum/
  29. Rosetta Code
    http://rosettacode.org/wi­ki/Rosetta_Code
  30. Mandelbrot set: Sinclair ZX81 BASIC
    http://rosettacode.org/wi­ki/Mandelbrot_set#Sinclair_ZX81_BA­SIC
  31. Nástroj Dialog
    http://invisible-island.net/dialog/
  32. Projekt Zenity
    https://wiki.gnome.org/Pro­jects/Zenity
  33. Xterm256 color names for console Vim
    http://vim.wikia.com/wiki/Xter­m256_color_names_for_conso­le_Vim
Našli jste v článku chybu?