A Ruby programozási nyelv

Ruby/Tk

Bevezetés

A Ruby alapértelmezett grafikus kezelői felülete(GUI) a Tk, de léteznek más megoldások is pl.: GTK vagy OpenGL. A Tk a Tcl szkript nyelv GUI-jának indult, amikor John Ousterhout elkezdte fejleszteni. A Tk büszkélkedhet azzal az egyedülálló különbséggel a többi GUI-hoz képest, hogy ő az egyetlen multi-platform GUI. Mind Windoson, mind Mac-en, mind Linux fut és tökéletes megjelenését biztosít. Az alapvető komponense a Tk alapú alkalmazásoknak a widget. Egy komponenst gyakran ablaknak(window) is neveznek, hiszen a Tk-ban a "window"-t és a "widget"-et gyakran használják felcserélve. Tk alkalmazások olyan widget hierarchiát követ, ahol bármennyi widget elhelyezhető egy másik widgetbe és mindezt akár belerakhatjuk egy harmadikba, a végtelenségig folytatva a sort. A fő widgetre egy Tk programban a gyökér widgetként hivatkozunk és a TkRoot osztály egy új példányának létrehozásával hozhatjuk létre. A legtöbb Tk alapú alkalmazás ugyanazt ciklust követi: hozzuk létre a wigetet, helyezzük el az interfészre, majd végül kapcsoljuk össze a widget eseményeit eljárásokkal. Az interfész widgetjeinek méretét és helyzetét három geometria kezelővel tudjuk szabályozni: place, gird, pack.

Telepítés

A Tk GUI eszközkészlet alapvetően a TCL szkript nyelvhez készült, de azóta számos más nyelv is adoptálta, így a Ruby is. Habár nem a legmodernebb grafikus felületkezelő, de legalább nem platformfüggő, ingyenes és egyszerű grafikus alkalmazásokhoz kiváló. De mielőtt GUI-val rendelkező programokat tudnánk írni Rubyban telepítenünk kell a Tk könyvtárait és a közös interfészt is, amin keresztül a Ruby hozzá tud férni a Tk könyvtáraihoz. Ezt sokféleképpen megtehetjük, most csak a legegyszerűbb módot tárgyaljuk Windowsra és Linuxra is. Először nézzük a "wines" változatot. Fel kell telepíteni az ActiveTCL nyelvet a gépre, ami különbözik a Rubytól, de ugyanazok fejlesztették a két nyelvet sőt Tk-t is ugyanazok írták. Az ActiveTCL-t az ActiveState honlapjáról lehet letölteni, a telepítésével nem lehet probléma (habár a 8.5-ös verzió is kijött már sokan azt javasolják, hogy a 8.4-es verziót telepítsük). Ha a One-Click telepítőt használtuk a Ruby installálásához, akkor elvileg nincs más dolgunk a Ruby Tk "binding" már fen van a gépen, ha máshogy telepítettük régebben a Rubyt két lehetőség közül választhatunk. Vagy letöröljük és az előbb említett One-Clivk módszerrel újra felrakjuk vagy a bonyolultabb utat választunk. Ehhez Visual C++ lesz szükségünk, mivel a Ruby forráskódját kell teljesen újrafordítanunk. Egy bővebb leírás itt található erről a módszerről.
Linux alatt még ennél is egyszerűbb dolgunk van. Csak a libtctlk-ruby csomagot kell telepítenünk. Ez installálja nekünk Tk-t és a Ruby Tk bindingot is. Ezt a package-et elérhetjük valamilyen grafikus csomagkezelő segítségével vagy parancssorban a $ sudo get apt-get install libtcltk-ruby paranccsal.

Egyszerű Tk alkalmazások

A tipikus struktúrája a Ruby/Tk programoknak, hogy készítünk egy gyökér ablakot (a TkRoot egy példánya), widgeteket adhatunk hozzá, amivel felépítjük az interfészt, majd elindítunk egy fő ciklust a Tk.mainloop meghívásával. Egy egyszerű Hello World! alkalmazás így néz ki.

require 'tk' root = TkRoot.new { title "Hello, World!" } TkLabel.new(root) do      text 'Hello, World!'     pack { padx 15 ; pady 15; side 'left' } end Tk.mainloop

Először betöltjük a tk kiegészítő modult, aztán létrehozunk egy gyökér-szintű keretet a TkRoot.new paranccsal. Csinálunk egy TkLabel widgetet, amin a gyökér keretnek a gyereke, beállítunk pár tulajdonságot, mint például a feliratot. Végül elindítjuk a GUI vezérlő fő ciklust. Megjegyzendő, hogy nem muszáj expliciten meghatározni a gyökeret, de egy jó szokás, amitől nem kell eltérni. De az alábbi kód is működik:

require 'tk' TkLabel.new { text "Hello, World!" } Tk.mainloop

Ruby/Tk widget osztályok
Widgetek létrehozása roppant egyszerű. Csak venni kell a Tk dokumentációban található nevét a widgetnek és elé kell tenni a Tk prefixet. Így lesznek például a Label és Button widgetekből TkLabel és TkButton osztályok.  Ugyanúgy mint minden egyéb objektumnál a new kulcsszóval tudunk új példányt létrehozni. Ha nem adjuk meg, hogy ki legyen a widgetünk őse, akkor automatikusan a gyökér-szintű keret lesz az. De általában meg akarjuk határozni az ősét, sok más tulajdonságával együtt – pl.: szín, méret, stb. Arra is szükségünk lesz, hogy a widgettől szerezzünk vissza információt futás időben.
Alapvető beállítások
Ha megnézzük a Tk hivatalos leírását (amit a Perl/Tk-hoz írtak), azt vesszük észre, hogy a widgetekhez tartozó beállítások gyakran kötőjellel együtt vannak említve – úgy ahogy a parancs sori paraméterek kell megadni. Perl/Tk-ban a paraméterek hash-ben adódnak át, erre van lehetőség a Rubyban is, de kód blokkon keresztül is megtörténhet. A beállítás nevét metódusként lehet használni, aminek az argumentumai az értékek. A widgetek az első paramétert mindig a szülőnek tekintik, ez után jöhet egy hash vagy egy kód blokk. A következő két forma ekvivalens:

TkLabel.new(parent_widget) {   text    'Hello, World!'   pack('padx'  => 5,        'pady'  => 5,        'side'  => 'left') } # vagy TkLabel.new(parent_widget, text => 'Hello, World!').pack(...)

Egy apró különbség van a két módszer között. A változók hatásköre nem az, amire gondolnánk. A blokk a widget objektum környezetében értékelődik ki, nem a hívóéban. Ez azt jelenti, hogy a hívó objektum példányváltozói nem lesznek elérhetőek a blokkban de lokális és globális változókra ez nem igaz. A távolságokat alapvetően pixelbe kell megadni, de megfelelő előtagokkal más mértékegységek is elérhetőek: ``c'' (centiméter), ``i'' (inch), ``m'' (milliméter), or ``p'' (pont).
Nem csak meghatározhatjuk a widgeteink egyes tulajdonságait, de vissza is szerezhetünk tőlük információkat callback-ekkel és binding változókkal. Callback-eket nagyon egyszerű beállítani, a command tulajdonságnak kell megadni egy Proc objektumot, ami akkor fog meghívódni, ha a callback életre kell.

TkButton.new(bottom) {   text "Ok"   command proc { p mycheck.value; exit }   pack('side'=>'left', 'padx'=>10, 'pady'=>10) }

De egy Ruby változót és egy Tk widget érték stringjét is összetudjuk kapcsolni, méghozzá TkVariable proxy használatával. Ezt a következő példa demonstrálja. Figyeljük meg, hogy van a TkCheckButton beállítva. Mivel egy variable tulajdonságnak var referencia kell argumentumnak létrehozunk egy variable referenciát a TkVariable.new paranccsal. A mycheck.value a szerint lesz 0 vagy 1m hogy a checkbox be van-e jelölve. Hasonló módon járhatunk el a többi var referenciát támogató widget esetében is (pl.: rádió gombok, szöveges mezők).

mycheck = TkVariable.new TkCheckButton.new(top) {   variable mycheck   pack('padx'=>5, 'pady'=>5, 'side' => 'left') }

Természetesen dinamikusan, futás időben is lehet változtatni a widgetek beállításait. Minden widget támogatja a configure metódust, ami ugyancsak egy hash-t vagy az egy kód blokkot vár úgy, mint az új elem létrehozásánál. Ebben a példában a gomb megnyomására fog megváltozni a gomb felirata.

lbl = TkLabel.new(top) { justify 'center' text 'Hello, World!'; pack('padx'=>5, 'pady'=>5, 'side' => 'top') } TkButton.new(top) { text "Cancel" command proc { lbl.configure('text'=>"Goodbye, Cruel World!") } pack('side'=>'right', 'padx'=>10, 'pady'=>10) }

Minden widget rendelkezik számos, különböző beállítással, amik általában a megjelenésüket és viselkedésüket befolyásolja. Az elérhető beállítások természetesen attól függ, melyik osztállyal dolgozunk. Néhány fontosabb beállítás (a legtöbb): activebackground => String, background =>String, borderwidth => Integer, font=> String, offset => Sting, padx => Integer, pady => Integer, takefocus =>  Integer, tile => Image,

Egy lista a widget osztályokról, amelyekből tetszőleges grafikus interfészt lehet létrehozni:

A configure eljárás
A configure eljárás arra használja, hogy bármilyen widget beállítását lekérdezzük vagy beállítsuk. Például egy gombnak a színét könnyen be tudjuk állítani a gomb konstruktora után is:

require "tk" button = TkButton.new { text 'Hello World!' pack } button.configure('activebackground', 'blue') Tk.mainloop

Ahhoz, hogy egy aktuális értéket lekérdezzünk egy widgetről:

color = button.configure('activebackground')

Bármilyen paraméter nélkül is meghívható a configure, ekkor egy az összes beállítást és értéket tartalmazó listát fogunk visszakapni.

A cget eljárás: Általában a configure sokkal több információt biztosít, mint amire tényleg szükségünk lenne. A cget eljárás csak az aktuális értékkel fog visszatérni.

color = button.cget('activebackground')

Menük
A menük szinte minden alkalmazásban jelen vannak, ahol egy kicsi grafika megjelenik. A menüknek rengeteg fajtája létezik. Habár a Tk ezekből sokat biztosít is számunkra vannak olyanok, amelyeket nem találunk meg a Tk eszközkészletében. Figyeljünk arra, hogy a menüsorok már magukban is komplex widgetek. Egy menübár, ami számos opciót és almenüt biztosít, amikhez különböző parancsok vannak csatolva, valószínűleg még billentyűgomb nyomásokra is érzékenyek, ezek már egy kicsit többek egyetlen gombnál. De szerencsére maga a menü az ami nagyon összetett, nem az megvalósítása.

root = TkRoot.new() { title "Menu Example" } bar = TkMenu.new() sys = TkMenu.new(bar) sys.add('command', 'label'=>"Quit", 'command'=>proc { root.destroy }) bar.add('cascade', 'menu'=>sys, 'label'=>"System") file = TkMenu.new(bar) file.add('command', 'label'=>"Open", 'command'=>proc { puts "Open..." }) file.add('command', 'label'=>"Close", 'command'=>proc { puts "Close..." }) bar.add('cascade', 'menu'=>file, 'label'=>"Fájl") root.menu(bar)

Mit is csináltunk? A bar a mindenek felett álló menü. Ebben kap helyet a sys és fájl almenü, amihez hozzá kapcsoltunk egy-két parancsot, majd legvégül a felső szintű menüt (bar) hozzá adtuk, mindennek az őséhez, a gyökérhez. Az egész menü akkor fog látszódni, ha a Fájl vagy System menükre kattintunk és egy legördülő menüt fogunk látni. Vannak egyéb fajta menük is: command, elválasztó, cascade(submenü), jelölőgomb, rádiógomb. A Ruby/Tk összekapcsolásban egy extra osztály is helyet kapott, ami egy rövidebb módot ad menük és almenük implementálására. Annak ellenére, hogy ez a módszer nem az elsődleges módszer, amit a Tk javasol érdemes ezt a módszert használni, ha bonyolult szerkezetet akarunk előállítani. Zárójelben jegyzendő meg, hogy a Tk nem ismeri a menübár fogalmát, egyszerűen egy legfelső menüként értelmezhető.

menu_spec = [ [ ['File', 0], ['New File',  proc{new_file}], '---', ['Quit',      proc{exit}] ], [ ['Edit', 0], ['Cut',       proc{cut_text}], ] ] TkMenubar.new(nil, menu_spec, 'tearoff'=>false).pack('fill'=>'x', 'side'=>'top')

Vannak úgynevezett option menük is. Ezek több lehetőségből való választásra alkalmas menük készítésére szolgálnak. Hasonlít a fejlettebb GUI-k combobox-ára. Egy TkOptionMenubutton egy TkVariable típust vár második paraméternek (ne adjuk át nill-t), amit az alapértelmezett értéknek kell követni végül az értékek listája. Sajnos az alapértelmezett értéket is a listához fogja adni a Tk, még akkor is ha az már benne van. A példából látszik, hogy milyen egyszerűen lehet használni.

root = TkRoot.new() { title "Example Menu Event" } names = ["One", "Two", "Three"] var = TkVariable.new() button = TkOptionMenubutton.new(root, var, *names) button.pack

A Tk egy virtuális eseményt (MenuSelect) továbbít a menünek, amikor valamilyen aktivitást észlelt. Ezt az eseményt a következőképpen kaphatjuk el: button.menu.bind("<MenuSelect>") { p "Menu select!!! (#{var})" } Nem csak egy új érték kiválasztása minősül aktivitásnak, hanem a kijelölés változása is, az értékek választásánál.
Végül néhány szó a felugró menükről is. Ez a második módja a menük kezelésének és nyilvánvalóan ezek a menük fel fognak ugrani, amikor akarjuk, általában bal-egérgomb kattintáskor. Erre lesz szükségünk: some_widget.bind("Button-3") { |evt| menu.popup(x_root, y_root) } Ahol az x_root és y_root azért kell mert a felugró menü is egy legfelső ablak lesz, ami figyelmen kívül hagyja az ablak kezelőt.

Ruby/Tk geometria kezelő

A geometria kezelő feladata, hogy a widgetek úgy jelenjenek meg, ahogyan azt megkívánjuk. A geometria kezelés a Tk-ban a alá-fölé rendelt widgetek elképzelésre támaszkodik. A "master" widget az, amelyik legfelül helyezkedik el, tipikusan egy legfelső ablak vagy keret, ami majd a többi elemet tartalmazza, amiket "slave"-eknek szoktak hívni. A geometria kezelőre tekinthetünk úgy, mintha ez irányítaná a master widgetünket és ez dönti el, hogy mi jelenjen meg benne,
A geometria kezelő minden slave widgettől lekérdezi az eredeti méretét vagy, hogy ideális esetben mekkorának kéne megjeleníteni. Ez alapján és a program által biztosított információk alapján kéri meg a program a geometria kezelőt, hogy kezeljen egy bizonyos szolga widgetet.

Három fajta kezelő létezik:

Ruby/Tk Esemény kezelés

A Ruby/Tk az event loop esemény kezelés támogatja (ez a megközelítés az a konstrukció, amikor a program várja, majd lekezeli az eseményeket), ami az operációs rendszertől fogadja az eseményeket. A szokásos eseményekről van szó: egér gomb nyomás, billentyű leütés, egér mozgatás, ablak átméretezés és így tovább. A Ruby/Tk kezeli ezt a "végtelen ciklust". Meghatározza majd, hogy melyik widgetre vonatkozik az esemény és hogy mit kell csinálni. Az egyes widgetek tudják, hogy hogyan kell reagálniuk az eseményekre, például egy gomb megváltoztathatja a színét, ha a kurzor fölé megy és vissza változtathatja azt, ha elhagyja a gomb területét. Magasabb szinten a Ruby/Tk callback-eket hív a programban, ha valami jelentős történt a widgeten, Mindkét esetben vagy egy kód blokkal, vagy egy Ruby Proc objektummal tudjuk meghatározni, hogyan viselkedjen a program az eseményre vagy a callback-re.
A bind segítségével tudjuk az egyes widgetek eseményeit kód blokkokkal összekapcsolni. Most nézzünk egy példát, hogyan használható a bind  metódus egyszerű ablak műveletek esetében. A legegyszerűbb használata bind-nek, hogy egy stringet adunk át neki, ami az esemény neve lesz, majd megadjuk a kódot, ami az eseményt lekezeli. Például ahhoz, hogy az egyes gomb felengedését kezeljük le valamilyen widgeten a következő kódot kell írni:

someWidget.bind('ButtonRelease-1') { ....kód rész, ami lekezeli az eseményt... }

Minden esemény név tartalmazhat plusz módosítókat és részleteket. A következő modellt kell követnie: módosító-módosító-típus-egyéb/részletek. A módosító egy string (Shift, Control...), jelezve, hogy melyik módosító billentyűt használták. Tehát például egy olyan eseményt kezeléséhez, ahol a Ctrl nyomva tartása mellett kattint a gombra a felhasználó a következőt írhatjuk:

someWidget.bind('Control-ButtonPress-3', proc { puts "Ouch!" })

A típus a X11-es elnevezési konvenciókat követendő meghatározás, pl.: ButtonPressed, Expose. Az egyéb vagy részletek, ami általában azonosításra szolgálnak vagy billentyűzet szimbólumok a bemenet azonosítására.
Az eseménynek magának is lehetnek mezői, pl.: az esemény ideje vagy az aktuális koordináták. Ezeket a bind képes továbbítani event filed code-ok segítségével. Például az x és y koordináták megszerzése az egér mozgatásakor a következőképpen néz ki:

Canvas.bind("Motion", proc{|x, y| do_motion (x, y)}, "%x %y")

Számos Ruby/Tk widget tud callback-eket kiváltani, amikor a felhasználó aktiválja őket. A command hívást használhatjuk a kód vagy procedúra meghatározásra, ami a kiváltódáskor fog lefutni. Ahogy azt már korábban is láthattuk a command procedúráját akkor is létrehozhatjuk, amikor a widgetet definiáljuk:

helpButton = TkButton.new(buttonFrame) { text "Help" command proc { showHelp } }

de később is hozzá kapcsolhatjuk:

helpButton.command proc { showHelp }

Mivel a command metódus procedúrákat és kód blokkokat is elfogad, az előbbi kódot a következőképpen is írhattuk volna:

helpButton = TkButton.new(buttonFrame) { text "Help" command { showHelp } }

Alap esemény típusok, amiket a Ruby/Tk alkalmazásokban használhatunk:

CímkeEsemény meghatározása
"1" (one) Bal egérgomb klikk
"ButtonPress-1" Bal egérgomb klikk
"Enter"Egér belép egy területre
"Leave"Egér elhagyja a widgetet
"Double-1"dupla kattintás
"Control-ButtonPress-3"Jobb klikk a Ctrl-lal együtt

Dolgok szebbé tétele

Az alapértelmezett Tk talán nem néz ki olyan jól, mint azt a többi grafikus felülettől megszoktuk. Ezt a "hiányosságot" elsősorban a betűkészletek variálásával szokták enyhíteni. Az Emacs által használt font így érhetjük el: some_text_widget.font('9x15'). Ha egy olyan fontot állítunk be, ami nincs telepítve a rendszerünkön, akkor a fixed fontot állítja be a Tk. Egy másik módja a fontok kezelésének a TkFont osztály pl.: font = TkFont.new('courier'), font.configure('size'=>36). Ennek egyértelmű előnye, hogy magunk manipulálhatjuk a fontokat. ( {'weight'=>'bold', 'slant'=>'italic'} erre a kettőre van igazából szükség a többi beállítás megtalálható a Tk manuáljában). Ráadásul több widgethez is tudjuk így ugyanazt a fontot rendelni, míg az első módszer mindegyikhez külön fontot rendel. Fontok kilistázására a TkFont.families() függvényt lehet használni. Mindezek lehetőségek ellenére van néhány kritikus widget, ahol érdemes az arányos szépen megjeleníthető készletnél maradni, ilyenek például a bemeneti widgetek, a TkEntry vagy a TkText.

Fordítás Perl/Tk Dokumentációból

A dokumentáció legnagyobb részét, ami a Perl/Tk-hoz tartozik könnyen "lefordíthatjuk" Rubyra. De akad néhány kivétel: néhány metódus nincs implementálva Rubyban és vannak olyan extra funkciók is, amik viszont nincsenek dokumentálva. Amíg nem jelenik meg egy Ruby/Tk könyv addig a legnagyobb esélyünk az ismeretlen dolgokkal kapcsolatban, ha megkérdezünk másokat vagy esetleg a forráskódja után nézünk a dolgoknak. Az esetek nagy részében azonban remekül lehet boldogulni a Perles doksival is. Fontos, hogy a tulajdonságok hash ként is megadhatóak, de kód blokk stílusban is és, hogy ezeknek a blokkoknak a hatóköre nem az osztály példány, hanem a TkWidget, amit éppen használunk.