Obsah
1. Tvorba textového uživatelského rozhraní s knihovnou prompt_toolkit: základní prvky TUI
2. Výpis formátovaného textu na konzoli
3. Použití třídy HTML a základních HTML značek <b>, <i> a <u>
4. Specifikace barvy textu: použití značky <style> popř. nových pseudoznaček
5. Změna barvy pozadí textu; kombinace barvy popředí, pozadí i stylu výpisu
6. Obarvení výstupu s využitím lexeru z knihovny Pygments
7. Konstrukce výstupu se specifikací typů jednotlivých tokenů
8. Dialogové boxy nabízené knihovnou prompt_toolkit
9. Dialog pro zobrazení zprávy uživateli – message_dialog
10. Základní dialog pro výběr odpovědi typu Ano/Ne
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
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ů.
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“.
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.
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))
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 |
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.
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')])
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.
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/presentations. 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
Standardní dialogy
20. Odkazy na Internetu
- UTF-8 encoded sample plain-text file
http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-demo.txt - 4 Python libraries for building great command-line user interfaces
https://opensource.com/article/17/5/4-practical-python-libraries - prompt_toolkit 2.0.3 na PyPi
https://pypi.org/project/prompt_toolkit/ - python-prompt-toolkit na GitHubu
https://github.com/jonathanslenders/python-prompt-toolkit - The GNU Readline Library
https://tiswww.case.edu/php/chet/readline/rltop.html - GNU Readline (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Readline - readline — GNU readline interface (Python 3.x)
https://docs.python.org/3/library/readline.html - readline — GNU readline interface (Python 2.x)
https://docs.python.org/2/library/readline.html - GNU Readline Library – command line editing
https://tiswww.cwru.edu/php/chet/readline/readline.html - gnureadline 6.3.8 na PyPi
https://pypi.org/project/gnureadline/ - Editline Library (libedit)
http://thrysoee.dk/editline/ - Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/ - libedit or editline
http://www.cs.utah.edu/~bigler/code/libedit.html - WinEditLine
http://mingweditline.sourceforge.net/ - rlcompleter — Completion function for GNU readline
https://docs.python.org/3/library/rlcompleter.html - rlwrap na GitHubu
https://github.com/hanslub42/rlwrap - rlwrap(1) – Linux man page
https://linux.die.net/man/1/rlwrap - readline(3) – Linux man page
https://linux.die.net/man/3/readline - history(3) – Linux man page
https://linux.die.net/man/3/history - vi(1) – Linux man page
https://linux.die.net/man/1/vi - emacs(1) – Linux man page
https://linux.die.net/man/1/emacs - Pygments – Python syntax highlighter
http://pygments.org/ - Write your own lexer
http://pygments.org/docs/lexerdevelopment/ - Jazyky podporované knihovnou Pygments
http://pygments.org/languages/ - Pygments FAQ
http://pygments.org/faq/ - TUI – Text User Interface
https://en.wikipedia.org/wiki/Text-based_user_interface - 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/ - 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/ - Rosetta Code
http://rosettacode.org/wiki/Rosetta_Code - Mandelbrot set: Sinclair ZX81 BASIC
http://rosettacode.org/wiki/Mandelbrot_set#Sinclair_ZX81_BASIC - Nástroj Dialog
http://invisible-island.net/dialog/ - Projekt Zenity
https://wiki.gnome.org/Projects/Zenity - Xterm256 color names for console Vim
http://vim.wikia.com/wiki/Xterm256_color_names_for_console_Vim