Asztali alkalmazások készítése PHP-vel: PHP-GTK

A múltkor írásomban bemutattam, hogy a PHP – bár elsődleges arra lett kifejleszve – nem csak webes nyelvként használható. Ebben a leírásban még inkább közelítünk az asztali alkalmazások világához és egy működőképes program alapján megismerkedhetünk a PHP-GTK alapvető működésével.


Az ismerkedés alatt ebben a leírásban kevésbé a gyakorlatias bemutatást értem, helyette egy átfogóbb képet szeretnék adni a GUI-val rendelkező programok készítéséről, működéséről. A fentebb linkelt íráshoz hasonlóan itt is három részre osztható a leírás: a futtató környezet előállítása, egy alkalmazás bemutatása, majd az alkalmazáson keresztül a fontosabb dolgok leírása. Ellenben itt már – főként a phpdoc kommentek miatt – nagyságrenddel hosszabb kódról van szó, így azt a cikkben nem közlöm (csak idézek belőle), hanem letölthetővé teszem.

Azonban először a futtató környezetet kellene előállítani. Hogy jobban megértsük a működését, először is tisztázzuk, mi az a PHP-GTK! A GTK (Gimp ToolKit) eredetileg a GIMP képszerkesztő programhoz készült, azonban később attól elkülőnített, grafikus felületek lértehozására készített eszközkészlet. A két legnagyobb előnye, hogy több platformmal (Linux, Unix, Window, …) és több programozási nyelvvel (eredetileg C-ben íródott, de elérhető C++-hoz, Perl-hez, Ruby-hoz stb-hez is) is használható. A PHP-GTK ezen eszközkészletet adja a PHP programozó kezébe a php_gtk modullal.

A Parancssori programozás PHP-vel cikkben ismertetett módon a PHP-GTK futtatót is felvehetjük a Path-be, és indíthatjuk a php fájlnév.php mintára. A szükséges csomagokat letölthetjük a PHP-GTK hivatalos weboldaláról: tömörített állományként Windowsra előre fordított binárisok formájában. Érdemes letölteni mind a Windows binary with GTK+ 2.10 és Windows binary extension pack csomagokat is, majd közös könyvtárba kitömöríteni őket.

Az ext mappába kerülnek a modulok (extension), amelyeket a php-cli.ini fájlba beszúrt extension=kiterjesztes.dll sorral tölthetünk be. A cikkben bemutatott programhoz szükség lesz az imap modulra, így írjuk be az extension=php_imap.dll -t a fenti fájlba (lehetőleg a többi extension= sor közelébe, hogy egy helyen legyenek). Azonban, ha csak tehetjük, felesleges modulokat ne töltsünk be, feleslegesen fogyasztják a memóriát.

Lépjünk be abba a mappába, amelybe kitömörítettük a PHP-GTK fájljait, majd a következő kódot mentsük le teszt.php néven!

<?php
$msgbox = new GtkMessageDialog(null, 0, Gtk::MESSAGE_INFO, Gtk::BUTTONS_OK, "Megy a PHP-GTK!");
$reply = $msgbox->run();
$msgbox->destroy();
?>

Ezután adjuk ki a php-win teszt.php utasítást, majd ha mindent jól csináltunk egy ablakot kell látnunk egy OK gombbal, egy ikonnal és egy “Megy a PHP-GTK!” felirattal. Egyelőre a kóddal ne foglalkozzunk, azt majd később nézzük át.

Ha minden igaz, már van egy használható PHP-GTK2 telepítésünk, úgyhogy most nézzük meg a példaprogramot. Bemutató-jellegű leírásoknál nem szeretek használhatatlan példákat mutatni (tipikusan ilyenek a Hello world példák), inkább hasznos kódokon keresztül magyarázok. A mostani példaprogram sem kivétel ez alól (hasznosságát mindenki döntse el maga), de azoknak, akik több postafiókot (és mail klienst) használnak, jól jöhet. Én például nem szeretem, ha a tálcám tele van olyan programokkal, amelyeket nem használok, így a mail kliens(eke)t elsőként zárom be, és ha épp elfelejtem napokig nem nézem a leveleim.

A példaprogram alapvetően “csöndben” (bármilyen megjelenített grafikus elem nélkül) működik, beálítható időközönként megnézi postaládáink tartalmát és egy gomb formájában értesít minket, ha bármelyikben talált egy levelet. A gombra kattintva pedig elindítja a hozzárendelt mail-klienst, majd eltüntetni azokat a gombokat, amelyekhez ugyanaz a kliens van rendelve (pl.: Nálam két fiókok a Thunderbird kezel, egy harmadikat az Outlook, G-mail-t pedig a webes felületén használom). Elsődlegesen POP3 protokollra készült, viszont megfelelően beállítva még pl. a Google címünket is képes ellenőrizni. Most még időzzünk el itt egy kicsit, és nézzük át hogyan lehet használni a programot!

Maga a program két (három) fájlt használ alapbeállításban: a mailnotifier.php-t, a mailboxes.ini-t és egy mail.png-t (ez utóbbit csak akkor, ha van: a program ikonját állíthatjuk be vele). A mailnotifier-t csak akkor kell módosítanunk, ha a nyelvet szeretnénk megváltoztatni (jelenleg angol nyelvű, angol kommentekkel – mégiscsak ez az Internet általános nyelve). Ehhez a kód elején deklarált konstansok értékét kell átírnunk. Két – opcionális – parancssori paramétere lehet, az egyik a /?, amely egy súgót jelenít meg, a másik a “-v”, amivel a horizontális elrendezés helyett függőleges elrendezést kapunk.

Postafiókjainkat a mailboxes.ini módosításával vihetjük fel, a következő formában:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
; Pop3 fiókhoz a következő formát használjuk:
;(a kommentet jelző soreleji ;-ket távolítsuk el!)
; Az Interval azt adja meg, hogy hány percenként ellenőrizzük a mail-fiókot
; Az Icon (opcionális) paraméter adja meg, hogy a gombon milyen ikon jelenjen meg (ajánlott méret: 16 * 16 pixel)
;
; Ha bármelyik érték szóközt is tartalmaz, tegyük " jelek közé!
;[Postfiók neve]
;Host=mail.szolgaltato.hu
;Port=110
;Username=felhasznalonev
;Password=jelszo
;Interval=5
;Icon=foobar.png
;Client="C:\Program Files\Mozilla Thunderbird\thunderbird.exe"
 
; Amennyiben titkosítot csatornát, esetleg más protokollt használunk,
;a Host és Port helyett használhatjuk az IMAPStr-t, amely az imap_open
;második paramétere lesz, így a pontos leírásához nézzük meg a
; http://php.net/imap_open oldalt!
; Példa:
; A Google-mail használatához engedélyeznünk kell a
;gmail webes felületén az IMAP hozzáférést!
;[Google]
;IMAPStr="{imap.gmail.com:993/ssl/imap}"
;Username=felhasznalonev@gmail.com
;Password=jelszo
;Interval=1
;Client="http://gmail.com"
;Icon="g-mail.png"

Ezzel a módszerrel felvihetünk akárhány postafiókot. Látható, hogy nem csak futtatható állományok adhatóak meg a kliens programnál, hanem fájlok/webcímek is; később látni fogjuk, hogy ezért buktuk a hordozhatóságot, csak Windows alatt használható a program. Nálam például 4 beállított fiókkal működik, mindhez van beállítva ikon, így ilyesmit kapot, ha három fiókomban van e-mail:

Megjegyzés: a programot beállíthatjuk automatikus indításúra, ehhez indítsuk el a regedit programot (Start -> Futatás (Run) -> regedit), majd keressük meg a HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run útvonalat a bal oldali fában és jelöljük ki. A jobb oldalon ezután jobb klikk -> Új (New) -> Karakterlánc (String), névnek írjunk be valamit (pl.: Mail Notifier), majd duplaklikkelés után értéknek a következő szöveget:

"C:\A\PHP\Futattó\Útvonala\php-win.exe" "C:\A\MailNotifier\útvonala\mailnotifier.php"

Ha felvittük a PHP-GTK-t a Path változóba, akkor elég a php-win majd a mailnotifier.php útvonala.

Amikor már eleget teszteltük (és a megtalált bugokról írtunk egy privát üzentet a fórumon BlackY-nek), akkor nézzünk bele egy kicsit a kódba. A kód hosszától (9 kilobájt) ne ijedjünk meg, több, mint fele komment (a valós kód 4 kb körül van)!

Mielőtt tovább megyünk, nézzük meg egy grafikus felület működését. Az itt leírtak természetesen nagyon messze állnak a valóságtól, ezeket a feladatokat az operációs rendszer (az ablakkezelő) látja el, viszont fontos még az elején kiemelni. Egy egyszerű példát vegyünk, “GUI”-nk alapjául: egy 28 * 7 karakteres terminál már elég hozzá. Az l alatt levő karakter jelölje a kurzort. (Valójában ez egy Text User Interface (Tui), viszont működése emlékeztet a GUI-kéhoz: a WIMP-ből (Windows, Icons, Menus, Pointers – Ablakok, ikonok, menük, mutató eszközök) az ablakot, a menüt és a pointert magában foglalja)

Hogy ebbe a “dobozba” életet leheljünk, egy hurok ismétléses vezérlési szerkezetben bizonyos időközönként lekérdezzük, történt-e változás az egér állapotában: arréb lökték-e, lenyomták-e valamelyik gombot. Ha megmozdították, akkor frissítjük a képet, a “fekete dobozt” átrakjuk a megfelelő helyre, ha lenyomták a gombot, akkor megvizsgáljuk, hogy a kurzor épp melyik “elemen” állt (ha maradunk ennél a szövegnél, akkor megnézzük, hogy hányadik karakter a fekete doboz a kint levő képben, ha ez a szám X*29 + 4 és X*29 + 13 között (X eleme {3,4,5}) van, akkor végrehajtjuk az “OK” “gomb” lenyomásához tartozó kódot, ha X*29 + 19 és X*29 + 28 között (X eleme {3,4,5}) van, akkor a Mégse gombhoz tartozóét. Egyébként pedig folytatjuk az ismétlést. Pszeudó-kódban:

1
2
3
4
5
6
7
8
9
10
11
12
13
while( true ) {
	if( mouse_has_been_moved() ) {
		set_cursor_pos(mouse_movement_horizontal(), mouse_movement_vertical());
	}
	if( mouse_has_been_clicked() ) {
		if(91 < get_cursor_pos() < 100 OR 110 < get_cursor_pos() < 119 OR 139 < get_cursor_pos() < 148) {
			button_ok_onclick();
		}
		else if(106 < get_cursor_pos() < 115 OR 135 < get_cursor_pos() < 144 OR 163 < get_cursor_pos() < 172) {
			button_cancel_onclick();
		}
	}
}

Ismét kiemelném, hogy ez egy nagyon leegyszerűsített példa, és a valós megoldásokhoz alig van köze. Viszont az eseményvezérelt programozás szemléltetésére jó: a második if szerkezeten belül látható, hogy amennyiben valamelyik gombra kattintottunk, meghívjuk az ahhoz rendelt eseménykezelőt. Ezt nevezzük Eseményvezérelt (event driven) programozásnak.

A weben használt kódjainknál megszokhattuk, hogy A után B utasítás hajtódik végre. Eseményvezéreltségnél nem tudhatjuk előre, hogy mi fog legközelebb történni (melyik esemény áll be), például a böngészőben nem tudhatja a kód előre, hogy a felhasználó a Refresh gombra fog kattintani, esetleg a kedvencek menüt nyitja le vagy bezárja a böngészőt. A böngésző vár, hogy a user csináljon valamit, legyen az a bezárás gombra kattintás, a refresh gomb, akármi. Ezt a várakozást egy központi függvény “végzi”, PHP-Gtk alatt ez lesz a Gtk::main() függvény.

A böngésző kapcsán érdemes még szót ejteni még egy dologról: gondoljunk csak a window.onload, element.onclick és hasonló JS kódjainkra: az elv ugyanaz, amikor egy esemény bekövetkezett (betöltött az oldal, a user ráklikkelt valamire stb.), a böngésző szól a JavaScript futtatónak, ami meghívja az erre az eseményre beállított eseménykezelőt)

Általában a Gtk programjaink a következőképp épülnek fel: felépítjük azokat az ablakokat, amelyek láthatóak induláskor, beállítjuk az eseménykezelőket, majd meghívjuk a Gtk::main() metódust. Innentől kezdve a felhasználóval való kommunikációt a Gtk végzi, az frissíti a megjelenített ablakok(at), az értesíti a kódunkat az események beállásáról (a user bezárta az ablakot, a user ide kattintott) stb. Ha most ránézünk a példaprogram kódjára, a legalján a következő sorokat láthatjuk:

1
2
$mcw = new MailNotifierWindow(parse_ini_file('mailboxes.ini', 1), $orientation);
Gtk::Main();

A MailNotifierWindow osztály példányosításakor a konstruktor (példányosításkor meghívásra kerülő metódus) létrehozza a GUI elemeket, beállítja az eseménykezelőket és elindítja az időzítőket (ezekről később). Majd a Gtk::Main() híváskor a Gtk veszi át az irányítást, megjeleníti a megjelenítendő ablakokat és elemeket, elkezdi a fő ciklust.

Annak, hogy a PHP-ben névtelen függvények létrehozására csak a viccnek is rossz create_function() függvény áll rendelkezésünkre a GTK programozáskor előnyei is vannak: mivel a create_function-t nem (nagyon) fogjuk használni, hanem rendes függvényeket készítünk, következetes elnevezési elveket használva később is egyszerűen áttekinthetővé tehetjük a kódunkat. A fenti pszeudókódban használtam a button_cancel_onclick() függvénynevet. Ugyanezen az elven, a “Cancel” feliratú gomb lenyomásakor (felengedesékor, egész pontosan) végrehajtandó függvény lehet például: button_cancel_onreleased. Az első rész az elem típusa (gomb), a második egy azonosító (cancel), a harmadik pedig az on string és az esemény nevének együttese. Ha a gombot úgy hozzuk létre, hogy külön osztályt származtatunk neki a GTKButton() osztályból, akkor az első két rész elhagyható, és csak a onreleased() lehet a metódus neve.

A fenti “teszt.php” kódjában nem volt Gtk::main() hívás, ott a run metódus indítja a ciklust, majd tér belőle vissza, amikor a user választ adott. Ez a metódus a GtkDialog osztálynak és az abból származtatott osztályok sajátja, így saját ablakoknál nem használható (hacsak nem a GtkDialog-ból származtatjuk őket). Ennek használatakor azonban a Gtk fő hurok az eredeti ablakunkra (parent ablak) vonatkozóan felfüggesztésre kerül, így a szülő ablak inaktívvá válik az új ablak bezárásáig, vagy amíg nem érkezik válasz a felhasználótól.

Most jött el az ideje, hogy a PHP-GTK dokumentációt is linkeljem. Ugyanis nagyon gyakran fogjuk ezt böngészni, ha PHP-GTK-val dolgozunk, és nagyon hasznos. Minden esemény (itt Signal néven futnak), minden osztály és azok minden metódusa megtalálható itt. És fontos ismernünk a címet, elég csak a megnézni az osztály hierachiát és azt több, mint száz osztályt, amit használhatunk.

Az eseménykezelők beállítása a connect_* metódusokkal (connect, connect_after, connect_object, connect_object_after, connect_simple, connect_simple_after) történik. Az egyes osztályok Signaljeit lekérdezhetjük a következő módon:

1
2
3
<?php
var_dump(GObject::signal_list_names(Osztaly::gtype));
?>

Többnyire a fő ablakunk bezárásakor szeretnénk kilépni a programból is (kivételhez nem kell messzire menni: a példprogram), így az ablakunk destroy eseményéhez kössük a Gtk::main_quit() metódust.

1
2
3
4
<?php
$window = new GTKWindow();
$window->connect_simple_after('destroy', array('GTK', 'main_quit'));
?>

Természetesen ezeken az eseményekezelőkön kívül is megkaphatjuk a vezérlést a Gtk-tól: időzítők használatával (Gtk::timeout_add(), Gtk::timeout_remove), az üresjáratban meghívásra kerülő függvényekkel (Gtk::idle_add(), Gtk::idle_remove()) valamint ha mi magunk hívjuk meg a main függvény egy-egy lefutását (Gtk::main_iteration(), Gtk::main_iteration_do()). Előbbit használjuk a példaprogramban az e-mail fiókok ellenőrzésére.

Az eseményvezérletségről kezdésnek talán ennyi elég, ha ezt a különbséget értjük, akkor a Gtk manual alapján nekiállhatunk saját programjaink megírásának.

Még fontos kiemelni, hogy miért is lett csupán Windows-kompatibilis a példakód: a PHP-GTK programok meglehetősen bonyolult felépítése miatt. Négy szint működteti a kódjainkat: a Windows, a PHP értelmező (php-win.exe), a PHP-GTK modul (ext/php_gtk2.dll) és a GTK (libgtk-win32.2.0.0.dll). Nagyjából a következők történnek a háttérben: a PHP elér a Gtk::main() hívásig, értesíti a PHP-GTK-t, ami értesíti a GTK-t, amely elindítja a fő ciklust (közben kommunikál a Windowssal). Amikor bármilyen esemény bekövetkezik, “visszaszól” a PHP-GTK modulnak, ami szól a PHP-nek, ami elindítja az eseménykezelőt – és itt van a problémás rész: a fő ciklus várakozik, amíg az esemény-kezelő nem végez. Így a grafikus felület nem kerül frissítésre (“kifagy a program”). Éppen ezért a PHP beépített programfuttató függvényei (exec, system, passthru, proc_open, popen) esélytelenek, mivel mind túl sok időt tölt el, mielőtt visszaadná a vezérlést a PHP-nek. Kerülő megoldásként egy COM objektumot (WScript.shell) betöltve indítjuk el a kliens programot, így elég gyorsan visszakapjuk a vezérlést ahhoz, hogy ne tűnjön a programunk lefagyottnak. Cserébe feladtuk a Linux támogatását, mivel a COM objektumok csak Windows alatt támogatottak. Éppen ezért, ha egy sokáig tartó eseménykezelőt írunk, akkor érdemes a Gtk::main_iteration() függvényét hívogatni, amikor éppen megkaptuk a vezérlést vagy nagyon optimalizálni a kódunkat.

A PHP-GTK hasznos eszközünk lehet, mind ha gyorsan egyszerűbb programokat akarunk összeütni, mind nagyobb fejlesztésekkor. A több operációs rendszer támogatása, a jól felépített objektum-rendszer és a könnyű tanulhatóság mind mellette szól. Ha bármilyen kérdés van a PHP-GTK-ról a fórum megfelelő topicjában fel lehet tenni!

4 HOZZÁSZÓLÁS

  1. Szia!

    Ne haragudj de az írást olvashatatlanná teszik a jobb oldali kis ablakok. A sorok végei teljesen nem látszanak a szövegtől ami jelentősen megnehezíti az olvasást. Firefox és IE alatt egyaránt ez a helyzet, nem lehetne valahogy orvosolni ezt a problémát?

  2. Én köszönöm, hogy most már rendesen eltudom olvasni ezt a remek leírást :)

HOZZÁSZÓLOK A CIKKHEZ

Kérjük, írja be véleményét!
írja be ide nevét