Bystroushaak's blog / Czech section / Programování / Reflexe grafických rozhraní

Reflexe grafických rozhraní

Před nějakou dobou proběhla na abclinuxu diskuze o desktopu, kde byl nakousnut i Smalltalk a jeho GUI, kde jsem zmíňil „morphic halo“ jako prvek, který smalltalkovským GUI umožňuje reflexi. V diskuzi jsem byl požádán o další informace, proto vám dnes přináším článek na téma reflexe grafického rozhraní nejen ve Smalltalku.

Reflexe

In computer science, reflection is the ability of a computer program to examine, introspect, and modify its own structure and behavior at runtime.

Reflection (computer programming). Wikipedia [online]. [cit. 2017-01-12].

Volně přeloženo:

Reflexe je v teoretické informace schopnost počítačového programu za běhu procházet, zkoumat, a modifikovat svou vlastní strukturu a chování.

V případě Objektově Orientovaného Programování (OOP) to typicky znamená, že je možné zjišťovat informace o objektech, například jméno třídy, ze které jsou instancovány, či kontrolovat přítomnost atributů, nebo metod. Existují ale také programovací jazyky, které takto mohou modifikovat cokoliv pomocí vyhodnocování datových struktur. K typickým zástupcům těchto jazyků patří například Lisp.

V případě grafického rozhraní je reflexí chápána možnost modifikace „oken“ a jejich chování, ale také možnost modifikace samotných programů, které „okna“ generují.

Reflexe grafického rozhraní v X Window / Gnome

Budu předpokládat, že všichni znáte a používáte grafické uživatelské rozhraní v nějaké jeho formě. Já osobně používám Linux Mint s grafickým prostředím Mate, což je fork Gnome 2. Toto prostředí je ve svých principech podobné všem ostatním linuxovým grafickým rozhraním s okny - myší a klávesnicí můžete pohybovat s okny na jedné, či více plochách. Okna můžete zavírat, minimalizovat, maximalizovat a tak podobně.

Grafické rozhraní v typických linuxových desktop managerech funguje všechno podobně. Program pomocí bindingů (napojení) na knihovny grafického serveru (xlib) generuje po svém spuštění nějaké „okno“. Grafický server reaguje na informace, které jsou mu bindingy předávány a uživatelské zprávy posílá zpět programu, který na ně může, ale nemusí nějak reagovat.

Princip fungování X Window System. Zdroj: Volume One: Xlib Programming Manual.
Detailnější schéma principu fungování X Window Systému. Zdroj: google dotaz v obrázcích na „x window system architecture

Co se týče běžného uživatele, pro něj většinou žádná reflexe kromě té, kterou mu nabízí samotný program, přítomná není. Uživatel může myší či klávesovými zkratkami okno zmenšovat, zvětšovat, různě přesouvat a případně zavřít. Pokud mu to neumožní program, tak například nemůže změnit titulek běžící aplikace, či přidávat a ubírat položky v menu. Většina programů podobné chování uživatelům nenabízí, i když jsem již potkal takové, které tuto funkcionalitu umožňovaly.

Pokročilý uživatel má možnost používat nástroje, které jsou typicky s X Window systémem distribuovány. Jedná se například o program xev, který uživateli umožňuje testovat klávesnici a myš.

Výstup z programu xev spolu s grafickým oknem. V konzoli je možné vidět ID okna aplikace, názvy eventů a kódy stisknutých kláves.

Dále pak třeba program xwininfo, který umožňuje zobrazit informace pro okno, na které kliknete:

Tyto informace je pak možné používat s programy jako třeba wmctrl, či devilspie, které na základě nich umožňují okna přesouvat a obecně ovládat například z nějakého scriptu, či programu, který si pokročilý uživatel napíše. Do velké míry se však stále jedná pouze o jiný přístup ke klasickému ovládání počítače, protože informace získané reflexí oken není možné využít například k přidání nového prvku do menu programu.

Pokud by uživatel něco takového chtěl udělat, nezbývalo by mu nic jiného, než získat přístup ke zdrojovým kódům aplikace, upravit je, zkompilovat a program znovu spustit s upravenými změnami. Obecně lze tedy říct, že ačkoliv klasický přístup k ovládání počítače reflexi nabízí, je velmi malá a špatně použitelná.

Reflexe v prostředí Endless OS

Před několika týdny se na abclinuxu mihla zprávička zmiňující Endless OS, která mě na první pohled zaujala velmi netradičním přístupem k reflexi grafického rozhraní. Součástí zprávičky je video (youtube záloha), kde je prezentována schopnost „otočit“ aplikaci a ihned začít upravovat zdrojové kódy:

Pod videem se nachází diskuze:

<Havoc Pennington> does it live-update the front of the window with your changes?
<Jono Bacon> I don't think it does right now, but I believe that is the plan. This is an early cut of the feature.
<jrb> definitely the plan and it does, though it's fragile.

Volně přeloženo:

<Havoc Pennington> dochází se změnami k úpravám přední strany okna?
<Jono Bacon> Nemyslím, že teď ano, ale věřím, že takový je plán. Jedná se o první verzi téhle funkcionality.
<jrb> rozhodně je to v plánu a funguje to tak, i když je to křehké

V podstatě se jedná o podobnou reflexi, jako v případě klasického desktopu, ale zajímavě zautomatizovanou tak, že kroky [editace - kompilace - běh aplikace] jsou notně usnadněny a zjednodušeny.

Osobně musím říct, že na mě toto demo velmi zapůsobilo. Rozhodně se jedná o krok zajímavým směrem a budu sledovat, jak moc se ujme. Endless OS jsem si stáhl a chtěl vyzkoušet možnosti úpravy aplikací, v současném release zatím ale tato funkcionalita naneštěstí není přibalená.

Doufám, že se nebude jednat jen o věc pro vývojáře a dokáži si představit systém, kde by uvedená funkcionalita byla v každé části grafického rozhraní a každé aplikaci. Zatím však bohužel nic takového neexistuje.

Reflexe grafického rozhraní ve Smalltalku

Smalltalk je programovací jazyk, který vznikl v roce 1972 a tehdy také fungoval jako „operační systém“ pro počítače Xerox Alto.

Zrekonstruovaný počítač Xerox Alto. Zdroj: http://www.righto.com/2016/10/restoring-ycs-xerox-alto-day-9-tracing.html

Smalltalk se celý točí kolem konceptu image - virtuálního počítače, ve kterém běží jak grafické rozhraní, tak i celý jazyk a aplikace. Virtuální počítač je možné pauzovat, ukládat a znovu spouštět tam, kde jste přestali.

Abych se vrátil k původnímu tématu reflexe grafického rozhraní ve Smalltalku - celý koncept je postavený kolem ovládacího prvku nazvaného „morphic halo“. Dovolím si ukázku s kódem;

Morphic halo

Začnu tím tím, že si vytvořím widget modrého čverečku, jako ukázkový morph (prvek grafického rozhraní Morphic), takové malé hello world. To udělám tak, že do Playgroundu (ekvivalent konzole) napíši aMorph := Morph new openInWorld.

Pak text vyberu, kliknu na „Do it“ a tím kód vykonám - berte to podobně, jako kdybych ho napsal do terminálu;

Tím vznikne proměnná aMorph, ve které je uložena reference na objekt, který se zároveň zobrazí na obrazovce.

„Morphic halo“ je dialog, který se objeví okolo prvku na obrazovce po použití speciální klávesovo-myšové zkratky ALT+SHIFT+Kolečko:

V tomhle případě je tím prvkem který chci prozkoumat modrý čtvereček (ukázkový demo morph), okolo kterého se objevila spousta ťuplíků, na které se dá kliknout. K čemu jednotlivé ťuplíky slouží? Pokud nad nimi podržíte myš, zobrazí se krátká nápověda. Zde je přehled všech nápověd:

Ťuplíky jsou vytvářeny dynamicky a u různých morphů (prvků grafického rozhraní) se zobrazují různé. Zde například chybí ťuplík pro rotaci morphu.

Objekty

Trochu odbočím; považuji za důležité vysvětlit, že jakýkoliv viditelný i neviditelný prvek ve Smalltalku je objekt. Modrý čtvereček na obrazovce je objekt. Tlačítka morphic halo jsou objekty. Co mají objekty společného? Reagují na zprávy. Zprávy fungují podobně, jako volání metod v jiných programovacích jazycích.

Morphic halo nedělá nic jiného, než že vám zpřístupňuje interface pro posílání zpráv objektu, který je na pozadí grafického rozhraní. Pokud s objektem pohnete pomocí morphic halo, nebo třeba změníte barvu či velikost, morphic halo posílá (někdy zprostředkovaně přes zbytek morphic frameworku) objektu zprávy, aby se změnil a ten se na základě toho změní.

Zajímavý je v tomhle ohledu ťuplík „Debug“, který vám umožňí otevřít si Inspector. Inspector je grafický pohled na interní stav objektu. Jakékoliv změny provedené v objektu se propisují do Inspectoru a naopak.

V Inspectoru můžete vidět jednotlivé properties objektu, jako třeba color (objekt Color blue - objekt vrácený jako odpověď na zprávu blue poslanou objektu Color), nebo bounds (rozměry čverce).

V dolní části Inspectoru pak máte textovou plochu, která funguje podobně jako playground, tedy jako jakýsi shell. Cokoliv co sem napíšete se vykonává v kontextu objektu. Například můžete poslat zprávu self color a dostanete zpět objekt modré barvy. Stejně tak můžete například přiřadit jinou barvu, čímž změníte objekt na pozadí, což změní i reprezentaci na obrazovce a čtverec třeba zčervená.

V Inspectoru je také možné zobrazit si poděděné metody objektu a spoustu dalších vecí:

Jakýkoliv prvek grafického rozhraní může být takto zkoumán a upravován. Následující sekvence obrázků to ilustruje:

Zde v okně označím pomocí morphic halo titulek.
Titulek přenesu vedle.
A pak ho zrotuji o 45° pomocí „morphic halo“ prvku vlevo dole.

Morphic halo je podle mého názoru geniální koncept, který vám umožňuje měnit prvky grafického rozhraní, zkoumat je a hrát si s nimi. Pokud vám něco nevyhovuje, prostě to vezmete a změníte dle vlastního uvážení. Díky tomu, že Smalltalk je postavený okolo konceptu image dává celý koncept smysl. Prostě si paměťovou mapu uložíte a příště začínáte tam, kde jste naposledy skončili, tedy i s upraveným grafickým rozhraním.

Přesto zde stále existují nevýhody tohoto konceptu a způsobu úprav grafického rozhraní, ke kterým se za chvíli vrátím.

Reflexe grafického rozhraní v Selfu

John Malloney, autor grafického toolkitu Morphic ho původně vytvořil pro programovací jazyk Self. Troufám si tvrdit, že v Selfu dává Morphic o hodně větší smysl, než ve Smalltalku. Proč? Protože Self je prototype-based (čti: na prototypech založený). Co to znamená?

V Selfu neexistují třídy. To má jeden ne zcela zřejmý vedlejší efekt; není v něm přítomný implicitní konstrukční a dekonstrukční mechanismus. Celý systém s tím počítá a je postavený kolem této myšlenky.

Je mi jasné, že všem nebude jasné co to znamená, proto to ještě rozvedu:

Class-based (čti: na třídách založené) jazyky, jako třeba Smalltalk, nebo Java, C++, či C# fungují na principu vytvoření popisu objektu, který chcete dostat. Tomu popisu se říká třída. Z tohoto popisu poté nějakým více, či méně přímým mechanismem vytváříte instanci, tedy samotný objekt, se kterým poté můžete pracovat. Při vytvoření instance objektu je zavolán konstruktor - speciální metoda, která nastaví nějaké hodnoty a například inicializuje reprezentaci objektu v grafickém rozhraní. Při rušení objektu je zavolán destruktor, který provede uklizení po objektu.

Rozdíl mezi třídou a instancí. Zdroj: Difference between instance and Object in Java.

Self patří k jazykům, které jsou objektově orientované, oproti spoustě ostatních jde však do extrému - výslovně zde není nic jiného, než objekty. Nejsou zde ani třídy, tedy popisy toho, jaký chcete vyrobit objekt. Když chcete vytvořit nový objekt, vezmete nějaký již existující (například prázdný objekt), ten zkopírujete a pak do něj přidáte, změníte, nebo uberete co v něm chcete mít. Místo dědičnosti je zde delegace, ať již přímá, nebo nepřímá, kde říkáte „když se v objektu něco nenachází, má se to hledat tady a tady“.

Programy v Selfu je možné psát, nebo skládat. To funguje tak, že vezmete nějaký objekt, přidáváte do něj jiné objekty a některé ubíráte a sem tam přidáte trochu kódu a jinde změníte vlastnosti, ať již kódem, nebo pomocí nástrojů grafického rozhraní.

Stejným způsobem můžete vzít samotné prvky grafického rozhraní, třeba tlačítko, pak ho zkopírovat, změnit jeho popisek a akci po kliknutí a pak ho přidat k dalším prvkům. Když jste s výsledkem spokojeni, celý si ho uložíte do vašeho původního objektu, který tvoří celou aplikaci.

Po celou dobu nemusíte (ale můžete) vytvářet popis toho co chcete dělat, můžete to přímo dělat na objektech v paměti pomocí reflexe. Pak tyto objekty učiníte několika kliknutími součástí vaší aplikace, kterou můžete dále distribuovat a serializovat do zdrojového kódu. O to se stará Transporter.

Důvod, proč jsem psal, že Morphic dává větší smysl v Selfu, než ve Smalltalku je nyní doufám jasný. Přestože v Smalltalku můžete vyvolat na libovolné komponentě Morphic halo a tuto komponentu živě zkoumat a upravovat, není zde přítomný princip, jak upravenou komponentu učinit součást vaší aplikace. I když vezmu titulek playgroundu a otočím ho o 180°, příště až otevřu playground, titulek bude opět stejný, jako byl na začátku. Kdybych ho chtěl změnit, musel bych změnit popis objektu - zdrojový kód ve třídě a otočit ho o 180° tam, i když jsem ho změnil přímo na své obrazovce.

V Selfu bych mohl změnit přímo graficky pouze prototyp titulku a tím změnit i všechny příště vytvářené kopie stromu objektů, ze kterých je titulek složený.

Samozřejmě, že pokud by byla v Selfu použita například metoda, která titulek vytváří zdrojovým kódem z popisu, tak by byla situace stejná jako ve Smalltalku, principiálně je ale možné pracovat čistě způsobem úprav přímo konkrétních objektů.

Autoři jazyka vás přímo podporují v myšlence, abyste aplikace a grafické rozhraní tvořili tak, že rozeberete kopii nějakého objektu, který se vám líbí a ze kterého budete vycházet. K tomu slouží i Factory window, které automaticky zkopíruje každý element, který z něj „chytnete“ myší. Ten poté můžete přenést na volnou plochu a začít z něj skládat aplikaci.

Core sampler

Autoři jazyka použili v Selfu poněkud jiný přístup, než je Smalltalkovské Morphic halo. Vytvořili takzvaný Core sampler:

To je aplikace (ehm, objekt), který vám ukazuje z čeho jsou složené objekty na které namíříte křížek v zaměřovači.

Core sampler v Core sampleru. Vidíme, že se skládá z coreSamplerMorph.
Core sampler v Core sampleru v Core sampleru. Vidíme, že zatímco první C.S. se skládá jen z coreSamplerMorphu, na vrcholu druhého je labelMorph sedící na proxyMorph a tvořící nápis „coreSamplerMorph“.

Core sampler vám umožňuje s morphy pracovat - díky němu můžete upravovat cokoliv na obrazovce, i když je to jinak nedotknutelné. Také můžete zobrazit Outliner pro daný objekt. Outliner plní podobnou funkci jako Inspector v Selfu - v podstatě se jedná o tabulkový pohled na stav objektu, zároveň je to ale také hlavní způsob, jak objekty vytvářet a upravovat, což Smalltalkovský Inspector není (ten slouží k průzkumu objektů, ne k psaní popisů objektů - tříd). Díky outlineru získáváte přístup přímo k objektu, který tvoří samotnou aplikaci, k jeho zdrojovému kódu a properties.

Menu pro otevření outlineru.
Outliner zobrazující různé sloty, z nichž některé obsahují odkazy na další objekty, jiné metody [slot prototype].
Ukázka změny property myLabel grafickým přetažením reference na outliner objektu stringu 'something else'.
Ukázka změny property myLabel posláním zprávy myLabel: 'different thing' objektu, který je reprezentován Outlinerem.

Ukázka konstrukce aplikace

Jak jsem psal, aplikace v Selfu je možné skládat. Jak to vypadá ukazuje následující sekvence obrázků:

Pro praktickou aplikaci by samozřejmě bylo nutné přidat ještě další prvky, jako tlačítka, různé gridy a zarovnání, aby aplikace tvořila skutečně okno. V principu je ale možné postupovat stejným principem. Jako zdroj všemožných widgetů slouží „Factory window“ - okno s ukázkami všemožných widgetů, které z něj můžeme přetahovat na plochu:

Jakmile by bylo vše hotovo, bylo by třeba ještě o-anotovat všechny objekty a přiřadit je do společného modulu. Pak by již bylo možné je například uložit na disk a verzovat pomocí gitu, či je distribuovat dalším uživatelům Selfu.

Kritika

Přestože Self nabízí velmi zajímavé možnosti vytváření grafického rozhraní, je nutné také zmínit negativní části.

Specificky jde o jistou nepřehlednost co do anotace objektů, kdy je někdy možné některé objekty špatně o-anotovat, což může mít za následek export pouze části aplikace.

Dále jsou občas problémy s mixováním procedurálního a prototypového stylu programování, kdy může být složité zjistit, kde se vlastně daný morph bere a jestli náhodou není generován procedurálně nějakou metodou, namísto aby byl uložen jeho prototyp v nějaké property. Pokud dochází ke kopírování morphu před jeho zobrazením, může být nesnadné zjistit, z čeho je morph vlastně kopírován.

Tyto problémy je obecně možné řešit lepšími nástroji, nutné je ovšem dodat, že vzhledem k minimální komunitě okolo Selfu (za poslední rok se konference účastnilo cca 10 uživatelů celosvětově) momentálně žádné takové nástroje nevznikají. I přes tyto nevýhody je však programování v Selfu zajímavé, když už pro nic jiného, tak pro svou odlišnost od zbytku světa.

Závěr

V blogu jsem prezentoval několik různých druhů a způsobů reflexe grafického uživatelského rozhraní, ani zdaleka se však nejedná o všechny.

Jestliže vás zaujal Self, časem se pravděpodobně dočkáte seriálu, nebo si můžete přečíst oficiální příručku. V případě, že vás zaujal Smalltalk, můžu doporučit http://pharo.org, resp. knihu Pharo by example, či starší tutorial Squeaku na youtube.

Pokud znáte nějaký jiný způsob reflexe grafického rozhraní, či pokud máte nějaké dotazy, nebo připomínky, nezapomeňte se ozvat v diskuzi.

Become a Patron