Helló Window! GTK alapú felhasználói felületek C, C++, Python nyelv˝u fejlesztése és tesztelése Pfeiffer Szilárd 1 2013. január 3.
1A
könyv létrejöttét a FSF.hu Alapítvány a Szabad Szoftver Pályázat[5] keretében támogatta
Tartalomjegyzék I
Bevezetés
1. Gimp Tool Kit 1.1. Általánosságok . . . . . . 1.1.1. Története . . . . . 1.1.2. Elérhet˝osége . . . 1.2. Részegységek . . . . . . . 1.2.1. GTK . . . . . . . 1.2.2. GDK . . . . . . . 1.2.3. GLib . . . . . . . 1.2.4. Cairo . . . . . . . 1.2.5. Pango . . . . . . . 1.3. Nyelvi változatok . . . . . 1.3.1. GTK minus minus 1.3.2. PyGobject . . . . . 1.3.3. Összehasonlítás . . 1.4. Kapcsolódó projektek . . . 1.4.1. Automata tesztelés
1 . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
2 2 2 2 3 3 3 3 4 4 4 4 5 5 5 5
2. Alapvet˝o ismeretek 2.1. A fejlesztés fogalmai . . . . . . . . . . 2.1.1. A GTK+ objektum-orientáltsága 2.1.2. A GTK alapfogalmai . . . . . . 2.1.3. A GTK+ m˝uködési sajátosságai 2.2. A tesztelés fogalmai . . . . . . . . . . . 2.2.1. Az ATK koncepciója . . . . . . 2.2.2. A Dogtail m˝uködése . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
7 7 7 8 9 10 10 11
3. A fejlesztés menete 3.1. Gimp Tool Kit . . . . . . . 3.1.1. Beszerzése . . . . 3.1.2. Fordítása . . . . . 3.2. Saját alkalmazások . . . . 3.2.1. Fordítás és linkelés 3.2.2. Futtatás . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
13 13 13 14 14 14 15
4. Els˝o ablakunk 4.1. Kódolási alapismeretek . . . . . . 4.1.1. Forráskód formázása . . . 4.1.2. Elnevezési konvenciók . . 4.1.3. Fejlécfájlok és importálás 4.2. Minimálisan alkalmazás . . . . . 4.2.1. Forráskód . . . . . . . . . 4.2.2. Fordítás és futtatás . . . . 4.2.3. Eredmény . . . . . . . . . 4.3. Tesztelés . . . . . . . . . . . . . . 4.3.1. Forráskód . . . . . . . . . 4.3.2. Futtatás . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
16 16 16 16 17 17 17 18 19 19 19 20
. . . . . .
. . . . . .
. . . . . .
i
5. Szignálkezelés dióhéjban 5.1. Fogalmak . . . . . . . . . . . . . . . 5.2. Szignálkezelés . . . . . . . . . . . . . 5.2.1. C, illetve C++ nyelv˝u változat 5.2.2. Python nyelv˝u változatok . . . 5.2.3. Fordítás és futtatás . . . . . . 5.2.4. Eredmény . . . . . . . . . . .
II
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
Alapvet˝o widgetek
6. Ablakok 6.1. Bevezetés . . . . . . . . . . . . . 6.1.1. Popup és toplevel ablakok 6.1.2. Window és dialóg . . . . . 6.1.3. Modalitás . . . . . . . . . 6.1.4. Tranziencia . . . . . . . . 6.2. Használat . . . . . . . . . . . . . 6.2.1. Létrehozás . . . . . . . . 6.2.2. Minimális példa . . . . . 6.2.3. Tartalmi elemek . . . . . 6.2.4. Vezérl˝o elemek . . . . . . 6.2.5. Megjelenítés . . . . . . . 6.2.6. Bezárás . . . . . . . . . . 6.2.7. Eseménykezelés . . . . . 6.2.8. Saját eseménykezel˝o . . . 6.3. Platformfügg˝o sajátosságok . . . . 6.3.1. Ablakkezel˝o . . . . . . . 6.3.2. Vezérl˝o elemek . . . . . . 6.4. A kód . . . . . . . . . . . . . . . 6.4.1. Fordítás és linkelés . . . . 6.4.2. Futtatás . . . . . . . . . . 6.4.3. Eredmény . . . . . . . . . 6.5. Tesztelés . . . . . . . . . . . . . . 6.5.1. Keresés . . . . . . . . . . 6.5.2. Státuszok . . . . . . . . . 6.5.3. Interfészek . . . . . . . . 6.5.4. Tulajdonságok . . . . . .
21 21 22 22 26 27 27
28 . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
29 29 29 29 29 30 30 30 32 32 33 36 36 38 40 40 40 40 41 41 41 41 41 41 43 43 44
7. Konténerek 7.1. Fogalmak . . . . . . . . . . . . . . . 7.1.1. Konténerek . . . . . . . . . . 7.1.2. Méretezés . . . . . . . . . . . 7.1.3. Elrendezés . . . . . . . . . . 7.2. Alapm˝uveletek . . . . . . . . . . . . 7.2.1. Létrehozás . . . . . . . . . . 7.2.2. Elem hozzáadása . . . . . . . 7.2.3. Elem eltávolítása . . . . . . . 7.3. Pa(c)kolás . . . . . . . . . . . . . . . 7.3.1. Elemek elhelyezkedése . . . . 7.3.2. Térköz, pányvázás és szegély 7.4. A kód . . . . . . . . . . . . . . . . . 7.4.1. Fordítás és linkelés . . . . . . 7.4.2. Futtatás . . . . . . . . . . . . 7.4.3. Eredmény . . . . . . . . . . . 7.5. Tesztelés . . . . . . . . . . . . . . . . 7.5.1. Gyerekek keresése . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
45 45 45 45 46 46 46 47 48 48 48 50 51 51 51 51 51 52
. . . . . . . . . . . . . . . . . . . . . . . . . .
ii
8. Megjelenít˝o eszközök 8.1. Fogalmak . . . . . . . . . . . . 8.1.1. Igazítás és helykitöltés . 8.1.2. Widgetek . . . . . . . . 8.1.3. Szövegformázás . . . . 8.1.4. Widgetek összefüggései 8.2. Alapm˝uveletek . . . . . . . . . 8.2.1. Létrehozás . . . . . . . 8.2.2. Megjelenítés . . . . . . 8.2.3. Kezelés . . . . . . . . . 8.3. Haladó m˝uveletek . . . . . . . . 8.3.1. GtkLabel . . . . . . . . 8.3.2. GtkTooltip . . . . . . 8.4. Tesztelés . . . . . . . . . . . . . 8.4.1. Objektum . . . . . . . . 8.4.2. Állapotok . . . . . . . . 8.4.3. Interfészek . . . . . . . 8.4.4. Viszonyok . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
53 53 53 53 54 54 54 54 56 57 57 57 57 58 58 58 58 59
9. Egysoros beviteli mez˝ok 9.1. Fogalmak . . . . . . . . . . . 9.1.1. Beviteli mez˝ok típusai 9.1.2. Interfész . . . . . . . . 9.2. Alapm˝uveletek . . . . . . . . 9.2.1. Létrehozás . . . . . . 9.2.2. Tartalom kezelése . . . 9.2.3. Csak olvasható mód . 9.2.4. Jelszavak kezelése . . 9.2.5. Szignálok . . . . . . . 9.3. Haladó m˝uveletek . . . . . . . 9.3.1. Ikonok . . . . . . . . 9.3.2. Folyamatindikátor . . 9.3.3. Iránymutató szöveg . . 9.3.4. Buffer . . . . . . . . . 9.3.5. Formázás . . . . . . . 9.4. Tesztelés . . . . . . . . . . . . 9.4.1. Keresés . . . . . . . . 9.4.2. Interfészek . . . . . . 9.4.3. Állapotok . . . . . . . 9.4.4. Tulajdonságok . . . . 9.4.5. Akciók . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
60 60 60 61 61 61 62 62 62 62 62 62 63 63 63 64 65 65 65 66 66 66
10. Választáson alapuló adatbevitel 10.1. Fogalmak . . . . . . . . . . . 10.1.1. Beviteli mez˝ok típusai 10.2. Alapm˝uveletek . . . . . . . . 10.2.1. Létrehozás . . . . . . 10.2.2. Kezelés . . . . . . . . 10.2.3. Szignálok . . . . . . . 10.3. Haladó m˝uveletek . . . . . . . 10.4. Tesztelés . . . . . . . . . . . . 10.4.1. Keresés . . . . . . . . 10.4.2. Státuszok . . . . . . . 10.4.3. Akciók . . . . . . . . 10.4.4. Viszonyok . . . . . . 10.4.5. Interfészek . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
67 67 67 68 68 69 70 70 70 70 70 70 71 71
iii
III
Összetett widgetek
11. Táblázatos megjelenítés alapjai 11.1. Fogalmak . . . . . . . . . . 11.1.1. Modell-nézet-vezérl˝o 11.1.2. Modell . . . . . . . 11.1.3. Nézet . . . . . . . . 11.1.4. Elemek elérése . . . 11.2. Alapm˝uveletek . . . . . . . 11.2.1. Létrehozás . . . . . 11.2.2. Kezelés . . . . . . . 11.2.3. Szignálok . . . . . . 11.3. Tesztelés . . . . . . . . . . . 11.3.1. Keresés . . . . . . . 11.3.2. Interfészek . . . . . 11.3.3. Állapotok . . . . . . 11.3.4. Akciók . . . . . . .
72 . . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
73 73 73 73 73 73 74 74 77 80 80 80 80 80 80
A. Licencelési feltételek
81
Tárgymutató
82
Táblázatok jegyzéke
86
Ábrák jegyzéke
87
Kódrészletek jegyzéke
88
Irodalomjegyzék
89
iv
rész I
Bevezetés
1
1. fejezet
Gimp Tool Kit 1.1.
Általánosságok
1.1.1.
Története
Mint megannyi szoftver a nyílt forrás több, mint negyedszázados történetében a GTK+ is felhasználói elégedetlenség eredményeként született. Peter Mattis és Spencer Kimball, a Kaliforniai Egyetem (Berkeley) hallgatói – az azóta is a nyílt forrású szoftverek egyik zászlóshajójának számító képszerkeszt˝o program, a GIMP fejlesztése közben – szembesültek az akkori id˝okben amúgy is csak kis számban rendelkezésre álló grafikus felhasználói felület készít˝o eszközök hiányosságival. Ezek leküzdésére döntöttek úgy – egyébiránt a Motif használata helyett –, hogy belekezdenek egy saját függvénykönyvtár fejlesztésébe. Az azóta nagykorúvá lett GTK+ több ízben is komoly átalakuláson ment keresztül. Ezek közül talán a legmeghatározóbb, hogy az eredetileg GUI eszközkészletként indult projekt jócskán túllépett eredeti keretein. Ennek lehet˝oségét a moduláris felépítés teremtette meg, mely kés˝obb még több ízben hasznára vált a projektnek. Azon részek, melyek nem közvetlenül függenek össze a grafikus felhasználói felületek fejlesztésével, vagy másutt is hasznosak lehetnek, külön modulokban kaptak helyet, egy flexibilis eszközkészletet hozva így létre, melyb˝ol mindenki pontosan annyit és csak annyit használ fel, amennyire feltétlenül szüksége van. A legutóbbi f˝overzió – vagyis a GTK+ 3 –, melyr˝ol a továbbiak szó lesz majd, nem csupán folytatja a hagyományokat, de igyekszik mindinkább kiszélesíteni a modularitás adta el˝onyok alkalmazási területeit.
1.1.2.
Elérhet˝osége
A GTK+ többféle formában, többféle operációs rendszerre és grafikus szerverre is elérhet˝o. A forma alatt ez esetben az értend˝o, hogy a függvénykönyvtár nem csupán forráskódként, hanem bináris változatban is letölthet˝o. Ez nyilván nem különösebb meglepetés, már csak azért sem, mert nyílt forrású szoftverr˝ol van szó. Itt érdemes megjegyezni, hogy a licenc a GTK+ – annak függ˝oségei, illetve számos kapcsolódó függvénykönyvtár esetén – a GNU Lesser General Public License, rövidítve az LGPL, ami összefoglalva annyit tesz, hogy nyílt, illetve zárt forrású szoftverek egyaránt fejleszthet˝oek a GUI eszközkészlet használatával, azzal a kitétellel, hogy a függvénykönyvtár általunk használt – esetleges módosításokat is tartalmazó – változatának forrását ügyfeleinknek kérésre át kell adnunk. A szokásjog, illetve a saját érdekünk azt diktálja ugyanakkor, hogy a javítások miel˝obb bekerüljenek a fejleszt˝ok által karbantartott változatba. Visszatérve az elérhet˝oséghez a forráskód mind verziókezel˝o rendszeren (Git) keresztül, mind archív állományok formájában letölthet˝o. A bináris változatok tekintetében elmondható, hogy mindaddig, amíg GNU/Linux alapú rendszereken dolgozunk, különösebb nehézségekbe nem fogunk ütközni, hiszen nagy valószín˝uséggel az általunk használt disztribúció mind a fejlesztéshez (devel), mind a hibajavításhoz (debug) szükséges csomagokat tartalmazza. Ugyanakkor a GTK+ egy multiplatform eszköz, vagyis joggal várható el, hogy több operációs rendszeren is m˝uködjenek az általunk megírt kódok. A már említett GNU/Linux disztribúciókon túl – melyet a továbbiakban els˝odlegesen, de közel sem kizárólagosan használunk majd – a GTK+ mind a Microsoft Windows, mind pedig az Apple Mac OS X rendszerein elérhet˝o. A használható grafikus szerverekr˝ol elöljáróban annyit érdemes megemlíteni, hogy a GTK+ portabilitásának két alappillére közül az els˝o az a megoldás, mely a felhasználó felület épít˝oköveinek – a GTK+ által használt terminológiával élve widget – implementációját elválasztja az azok megjelenítésére szolgáló rajzoló primitívek megvalósításától. Ennek révén biztosítható, hogy a GTK+ forráskód túlnyomó részének változatlansága mellett – csupán egy újabb, úgynevezett backend hozzáadásával – alkalmassá tehet˝o egy újabb grafikus szerver, vagy alrendszer (pl: X11, frame buffer, HTML5, . . . ) alá.
2
1.2.
Részegységek
A GTK 1 szervezésér˝ol fontos elöljáróban megemlíteni, hogy példaérték˝uen választja szét a funkcionalitás egyes elemeit, jól elhatárolt implementációs részegységre, melyek fejlesztése egymástól függetlenül, a GNOME projekttel együttm˝uködésben folyik. Ez a megoldás – számos el˝onye mellett – jár természetesen néhány nehézséggel is. A modulok csak publikus interfészeiken keresztül tudnak egymással kommunikálni. Ez egyrészr˝ol függetlenséget jelent a modulok belügynek tekinthet˝o implementációs részleteinek terén, viszont komoly kötöttség a publikus felület oldalán, lévén annak változatlanságától nem csak a küls˝o projektek, de az egyes GTK modulok is függenek. A hosszan fenntartandó állandóság hatásait jól példázza, hogy a GTK+ csak hat év után indított új f˝overziót (3.x), mely felszámolta a korábbi változattal való – helyenként csaknem teljesen felesleges – kompatibilitást. Az azóta eltelt id˝oben2 viszont vajmi kevés projekt döntött úgy, hogy átáll az új verzióra, még azzal együtt sem, hogy az egyszer˝ubb alkalmazások tekintetében ez különösebben komoly er˝oforrást nem igényel. Célszer˝uen tehát ezek az inkompatibilis váltások nem lehetnek túl gyakoriak. A következ˝okben sorra vesszük, mik azok a részegységek, melyek együtt a GTK C nyelv˝u változatát alkotják, s melyek természetesen a további nyelvi változatok alapjául szolgálnak.
1.2.1.
GTK
A GTK+ a grafikus felhasználó felületek (GUI) fejlesztéséhez szükséges felületi elemek (beviteli mez˝ok, rádiógombok, listák, dialógus ablakok, . . . ), azaz widgetek tárháza, vagyis a GTK keretrendszer lelke. A függvénykönyvtár a korábban leírtaknak megfelel˝oen csak a közvetlenül szükséges implementációt, vagyis a widgetek kirajzolásához, interakcióinak, adattárolásának megvalósításához szükséges kódok összességét jelenti. Ett˝ol persze némiképp többet, de err˝ol kés˝obb (1.4.1) esik szó.
1.2.2.
GDK
A GIMP Drawing Kit (GDK) – a GTK+ részeként terjesztett – alacsony szint˝u rajzolási és ablakkezelési feladatok megvalósítására, egyszersmind a magasabb szint˝u rutinok elöl történ˝o elfedésére szolgáló függvénykönyvtár. A GDK fontos szerepet tölt be a GTK különböz˝o platformok közötti hordozhatóságának megteremtésében, lévén az általa nyújtott – viszonylag sz˝uk kör˝u – funkcionalitást újraimplementálva a GTK+ alkalmas lehet egy újabb grafikus környezetben való futásra. Ezen funkciók alatt a már említett rajzolási primitíveken túl többek között rasztergrafikai feladatok, kurzor megjelenítése, illetve alacsony szint˝u ablakesemények implementálása értend˝o. A fentieknek köszönhet˝o, hogy az eredetileg csak az X Window System3 (X11) felett m˝uködni képes GDK, mára nemcsak egyéb Linux alapú szerveren (Wayland), de más operációs rendszereken (Windows, Mac OS X), s˝ot akár webböngész˝oben is m˝uködni képes4 . A grafikus alrendszer portolása mellett felmerül˝o problémák jelentékeny részét a GLib függvénykönyvtár oldja meg.
1.2.3.
GLib
A GLib a GNOME projekt egyik fundamentális eleme, mely történetileg ugyan köt˝odik a GTK+-hoz, mára azonban teljesen önállóvá vált. Eredend˝oen a GTK+ által használt, de attól függetlenül is létjogosultsággal bíró, platformfüggetlen kódok kiemelése egy külön függvénykönyvtárba, melyet számos – a GNOME, vagy a GTK projektekhez akár nem is kapcsolódó – szoftver5 alkalmaz. A meglehet˝osen szerteágazó funkcionalitáscsomag melyet a Glib megtestesít, röviden a következ˝oben foglalható össze; C programozási nyelven írt standard függvénykönyvtárak nem, vagy csak részben elérhet˝o, esetleg nehézkesen használható eszközök összessége. A teljesség igénye nélkül megemlítend˝oek a GLib adattárolói (láncolt listák, fák, dinamikus tömbök, hash táblák, . . . ), hordozható adattípusai (guint32, gboolean, . . . ), memória allokációs függvényei (g_new, g_allocator_new, g_slice_alloc, . . . ), gyakran használt formátumok (dátum, id˝o, URI, fájl- és könyvtárnév, . . . ) kezelésére szolgáló eszközei, konverziós algoritmusai (base64, endianness, karakterkódolás, . . . ) széles körben alkalmazott módszerek implementációi (XML, Glob, Regex, . . . ). Mindezeken túl a GLib tartalmaz néhány külön is említésre méltó alrendszert. 1 a GTK rövidítés alatt a továbbiakban az általánosságban vett grafikus felhasználói felület fejleszt˝ oi eszközt, míg GTK+ alatt ennek C nyelv˝u változatát értjük 2 a 3.0.0 verzió megjelenése id˝ opontja 2011. február 3 a Linux alapú rendszerek, a GDK születésének idejében, gyakorlatilag kizárólagos grafikus szervere 4 ezen szolgáltatáshoz eléréséhez websocket támogatásra van szükség a böngész˝ oben, illetve a Broadway elnevezés˝u backend bekapcsolására a GTK+ fordításakor 5 példának okáért a Compiz, a Midnight Commander, vagy a syslog-ng
3
GObject A GLib Object System egy C nyelven írt objektumorientált keretrendszer, mely megvalósít számos olyan funkciót (származtatás, virtuális függvények, típus, objektumok memória menedzsmentje, . . . ) melyek például a C++, vagy a Java esetén nyelvi szinten adottak. A GObject, mint osztály szolgál például alapjául a GTK+ által implementált minden egyes widgetnek. A kép ezzel azonban még közel sem teljes. A GObject implementál számos alapvet˝o fontosságú egyszer˝u (gdouble, gint, . . . ) és összetett típust, illetve támogatja ezek felhasználását saját típusok létrehozásakor, illetve a GObject-b˝ol származó osztályokban adattagként való elhelyezését. Emellett megvalósít egy – az objektumok állapotváltozásainak követésére szolgáló – kommunikációs alrendszert (signal). Fentiek létrehozásakor kifejezett cél volt a rugalmas b˝ovíthet˝oség és a könny˝u adaptálhatóság más nyelvekre. Ez utóbbi a kés˝obb (1.3) említésre kerül˝o nyelvi változatok egyszer˝u megvalósíthatóságának el˝ofeltétele. GModule A GModule egy dinamikus modulok betöltésére szolgáló függvénykönyvtár, mely rendelkezésre áll mindazon rendszereken, ahol a GLib is, elfedve ez egyes operációs rendszer különböz˝oségeit ezen a területen. GThread A GThread célja er˝osen hasonlatos az imént említett GModule-éhoz, vagyis biztosítani egy, a platformok sajátosságaitól független megoldást ezúttal nem a dinamikus modulbetöltés, hanem a szálkezelés tekintetében. GIO Az eddigieket folytatva a GIO is egy, a multiplatformos programozás során gyakran felmerül˝o probléma – jelesül a fájlok, fájlrendszerek, meghajtók kezelése – megoldására született. A megfelel˝o POSIX hívásokkal eddig is megvalósítható volt egy kvázi platformfüggetlen fájlkezelés, így ennek önmagában nem lenne számottev˝o haszna. A GIO ugyanakkor több, mint egy egyszer˝u POSIX hívásokat burkoló függvényhalmaz. A GObject-re támaszkodva egy magasabb szint˝u, dokumentumközpontú interfészt valósít meg.
1.2.4.
Cairo
A Cairo egy eszközfüggetlen6 kétdimenziós vektorgrafikai függvénykönyvtár, melyet kifejezetten a hardveres gyorsítókkal való együttm˝uködésre terveztek, s mellyel a GDK a kétdimenziós rajzolási feladatait végzi. Érdemes megemlíteni, hogy a Cairo nem a GNOME, hanem a freedesktop.org projekt része.
1.2.5.
Pango
A Pango szövegek képi formában történ˝o el˝oállításáért (rendering) és megjelenítésért (lay out) felel˝os a GTK-n belül, de természetesen a GTK-tól függetlenül is használható, lévén a függvénykönyvtár az el˝obbiekhez hasonlóan számos platformot támogat.
1.3.
Nyelvi változatok
1.3.1.
GTK minus minus
A gtkmm, illetve annak függ˝oségei adják a GTK projekt C++ nyelv˝u változatát. Ezek a függvénykönyvtárak wrapperek az eredeti C változat fölött, az ebb˝ol fakadó el˝onyökkel és korlátokkal együtt. Ezen kódok jelentékeny része wrapper mivoltukból következ˝oen generált, ugyanakkor számos helyen – ahol ez funkcionalitáshoz a programozási nyelvhez leginkább illeszked˝o megvalósításához szükséges – eredeti kódot is tartalmaz. A C, illetve C++ nyelv˝u változatok a lehet˝o legkisebb mértékben térnek el egymástól. Ez egyben azt is jelenti, hogy az egyes nyelvi változatok nem tartalmaznak a többihez képest többlet funkcionalitást. Nem lehet azonban eltekinteni az egyes programozási nyelvek adta lehet˝oségek el˝onyeit˝ol, hátrányaitól, melyek könnyebbé vagy nehezebbé teszik a GTK adott nyelven való használatát. Libsigc++ A gtkmm implementációjánál használt függvénykönyvtár, ami lehet˝ové teszi a szignálkezelés típusbiztos megvalósítását, mely a C változatnál – a nyelvi sajátosságok okán – nem adott. 6 értsd
hardvereszközökt˝ol független
4
GTK+
gtkmm
PyGObject
C
C++
Python
Implementáció módja:
natív
wrapper
binding
Objektumorinentált technikák használata:
közvetett
natív
natív
LGPL
LGPL
LGPL
Evolution, Firefox, Gimp, . . .
GParted, Inkscape, . . .
gedit
Nyelv:
Licenc: Ismertebb projektek:
1.1. táblázat. A GTK+, gtkmm, PyGObject összehasonlítása
1.3.2.
PyGobject
A Python változat – ahogy számos más egyéb nyelvi variáció is – alapjai gyökeresen megváltoztak a GTK+ új f˝overziójának megjelenésével. A korábbi – a gtkmm által is alkalmazott – módszer, az eredeti változatot rejti el, burkolja be (wrap), vagy egy köztes réteget képez a C, illetve a cél nyelv – esetünkben a Python – között. Ezen réteg többé-kevésbé természetesen automaták (pl: generátor szkriptek) révén jön létre, ugyanakkor igaz az, hogy nem közvetlenül az eredeti kódbázist használja a burkoló réteg létrehozására. Következésképpen a GTK+ publikus felületében bekövetkez˝o változásokat az egyes wrappereknek rendre követniük kell, holott a GTK+ kódjának írásakor is adottak azok a metaadatok, melyek mondjuk egy Perl, vagy Python elkészítéséhez szükségesek. GObject Introspection Az el˝obbi gondolatot tovább f˝uzve juthatunk el ahhoz a kézenfekv˝o kérdéshez, hogy miért nincsenek az említett metaadatok rögtön a GTK+ – illetve a GLib, Pango és a többi függ˝oség – kódja mellett, ahonnan kinyerve azokat az egyes nyelvi változatokat egyszer˝uen generálni lehetne. A GObject Introspection pontosan ezt célozza. Egy binding elkészítéséhez szükséges adatok a C nyelv˝u változatok – kódjában egy erre a célra meghatározott formátumban – megjegyzésként szerepelnek, a különböz˝o nyelv˝u változatok pedig ezt felhasználva jönnek létre.
1.3.3.
Összehasonlítás
Az egyes változatoknak megvannak a maguk – jellemz˝oen a programozási nyelv sajátosságaiból következ˝o – el˝onyei. Ilyenek lehetnek például a C nyelv, illetve a fordítók széles kör˝u elterjedtsége, a C++ azon sajátossága, hogy a nyelv nyújtotta módszereket, mint például az örököltetés, itt közvetlenül használhatjuk ki, vagy a Python nyelv˝u fejlesztés sebessége. Míg mondjuk a C nyelv esetében bizonyos funkciók kissé nehézkesen használhatóak, addig a C++ objektumorientált megközelítése mellett ugyanez a funkció játszi könnyedséggel elérhet˝o, vagy éppen a Python nyújtotta szkript környezet ad könnyebb, rugalmasabb kezelhet˝oséget. Az említett három változat tekintetében a f˝obb ismérveket a 1.1 táblázat tartalmazza.
1.4.
Kapcsolódó projektek
1.4.1.
Automata tesztelés
A grafikus felületek kapcsán sajnálatosan elhanyagolt terület az automata tesztelés, azon nyilvánvaló tény ellenére is, hogy a felhasználó épp ezeken a felületeken keresztül éri el az érdemi funkcionalitást és nyeri els˝o benyomásait a szoftverrel kapcsolatban, így ennek megjelenése, valamint helyes m˝uködése dönt˝o az alkalmazás kés˝obbi sikerességének tekintetében. Ezzel együtt igaz továbbá, hogy teljes kör˝u (end-to-end) megvalósított tesztelés mindenképpen a felhasználó felületr˝ol indított akcióval kell induljon és az ugyanott tapasztalt reakció ellen˝orzésével kell végz˝odjön. A GTK felhasználásával fejlesztett felületek tesztelésénél rendelkezésünkre áll a megfelel˝o keretrendszer, mely lehet˝oséget teremt, hogy az elkészült felületi elemek m˝uködését automaták segítségével teszteljük. A kés˝obbiek során bemutatásra kerül˝o mintapéldák esetén mindenütt kitérünk majd az azok kapcsán felmerül˝o tesztelési feladatok megoldásának mikéntjére. Most azonban lássuk nagy vonalakban hogyan is m˝uködik ez a tesztelési keretrendszer.
5
Accessibility Tool Kit Els˝ore talán egymástól távoli területnek t˝unik a szoftverek akadálymentesítése (accessibility), valamint a felhasználó felületek automata tesztelése, egy valami mégis összeköti o˝ ket. Ez pedig az a követelmény, aminek a szoftver mindkét cél elérése érdekében eleget kell tegyen, ami nem más, mint az alkalmazás vezérelhet˝osége bizonyos felhasználói interakciók kizárása mellett is. Az automata tesztelés esetén ez gyakorlatilag az összes eszköz (billenty˝uzet, egér, . . . ) kizárását jelenti, hiszen a felhasználót ez esetben teljes egészében a tesztelést végz˝o szoftver helyettesíti. A szoftverek akadálymentesítésének biztosítása egy speciális megközelítést igényel, mely a fogyatékossággal él˝o emberek szoftverekkel végzett munkájának megkönnyítését helyezi el˝otérbe. Ehhez az ATK csupán annyit tesz, hogy definiál egy interfészt, melyen keresztül az adott szoftvert el lehet érni. Indulva onnan, hogy egy adott alkalmazást ki lehet választani az összes aktuálisan futó alkalmazás közül, folytatva azzal, hogy le lehet kérdezni az általa megnyitott ablakokat, az abban lév˝o felületi elemeket (widget), egészen odáig, hogy az általuk tárolt értékeket (egy beviteli mez˝o szövege, folyamatindikátor értéke, . . . ), illetve állapotokat (rádiógomb kiválasztott állapota, beviteli mez˝o szerkeszthet˝osége, . . . ) írni olvasni lehet, rajtuk akciókat (gomb lenyomása, menüelem kiválasztása) végezhetünk. Gail Lévén az ATK lényegében csak egy interfész definíció, ahhoz minden esetben7 tartozik egy implementáció, mely az adott felületfejleszt˝oi rendszer m˝uködését megfelelteti az ATK által definiáltaknak. A GTK esetén ez az implementáció a Gail. Dogtail A Dogtail egy Python nyelven írt és Python nyelven használható tesztautomatizációs eszköz, illetve keretrendszer. Segítségével létrehozhatók felhasználói felületek – a már említett ATK interfészen keresztül – tesztel˝o szkriptek, többféle formában és módon is. Ami a módot illeti, lehet˝oségünk van egyrészr˝ol effektíve forráskód – azaz egy Python szkript – formájában létrehozni a tesztjeinket, vagy úgymond ”felvételt” készíteni magáról a tesztelésr˝ol, majd az így rögzített eseményeket mint tesztet visszajátszani. Ha az el˝obbi módszernél maradunk – amir˝ol a további részekben is szó esik –, akkor is két lehet˝oség adódik, hiszen a Dogtail rendelkezik egy procedurális, illetve egy objektumorientált megközelítés˝u API-val, melyek tetszés szerint használhatóak a tesztek elkészítésekor. Accerciser Mind az automata tesztel˝o szkriptek megírásakor, mind egy konkrét alkalmazás felületének feltérképezésére, mind pedig az ATK interfésszel történ˝o ismerkedésre alkalmas eszköz az Accerciser, mely az akadálymentesített szoftverek feltérképezésére szolgáló eszköz és mint ilyen pontosan azon adatok megjelenítésére és módosítására, valamint azon akciók végrehajtására alkalmas, amire a Dogtail szkriptek révén képesek vagyunk.
7 már amennyiben az adott grafikus felületfejleszt˝ oi rendszer – mint amilyen a GTK+, vagy mondjuk a Qt – biztosítani kívánja az ATK-n keresztüli elérést
6
2. fejezet
Alapvet˝o ismeretek 2.1.
A fejlesztés fogalmai
2.1.1.
A GTK+ objektum-orientáltsága
Ebben a fejezetben arra próbálunk mélyebben is rávilágítani, hogy bár a GTK+ ugyan C nyelven íródott, mégis számos az objektum-orientált nyelvek esetén megszokott terminológiát használ, s˝ot ezeket a nyelvi eszközök adta mértékben meg is valósítja. Ezért az objektum-orientált fejlesztés fogalmait, kifejezéseit joggal használjuk még akkor is, ha GTK+ nyelv˝u fejlesztésr˝ol esik szó. Azzal együtt, hogy az objektum-orientált mechanizmusokat nyelvi szinten a C nem, csak a C++, illetve a Python támogatja lehetséges ezekkel élni ezen változat esetén is. Lássuk mik lennének ezek és hogyan válik lehet˝ové alkalmazásuk a GTK+ esetén. Egységbezárás Az objektum-orientált alapelvek közül C nyelven is viszonylag jól biztosítható elvr˝ol beszélünk. A GTK+ meg is teszi, amit ebben a tekintetben meg lehet. Az adatstruktúrákat – jelen esetben widgeteket – és az azokon m˝uveleteket végz˝o függvényeket a lehet˝oségekhez mérten egységként kezeli, valamint elrejti o˝ ket a külvilág el˝ol. A GTK+ minden saját makrót/függvényt GTK/gtk prefixszel lát el (a GLib esetén ez pusztán csak egy kis, illetve nagy g bet˝u). Egy adott részterület – például egy widget – saját „névtérrel” is rendelkezhet, azaz újabb prefixet vezethet be1 . Ezeket egymástól, illetve a „valódi” funkciót jelöl˝o nevekt˝ol _ (aláhúzás) jellel választjuk el. A C++ wrapper esetén – kihasználva a kézenfekv˝o nyelvi lehet˝oséget – a prefixek szerepét természetesen a névterek, illetve az osztályok veszik át2 . Ezen prefixelt makrók/függvények els˝o paramétere minden esetben a prefix által meghatározott típusú objektum, el˝osegítve ezzel is ezen függvények, illetve az általuk kezelt objektumok egy egységként való kezelését. A privát adatok külvilág elöl való elrejtésére a C nyelven erre a célra széles körben alkalmazott átlátszatlan mutatókat3 (opaque pointer) használja a GTK+, ami lehet˝ové teszi az objektum által használt adatszerkezetek, implementációs módszerek elfedését a publikus interfész használó kódok elöl. Örökl˝odés Megoldott a widgetek egymásból történ˝o származtatása, s˝ot felhasználói widgetek is definiálhatóak a már meglév˝oekre támaszkodva. Az kód újrahasznosítását jól mutatja az a tény, hogy a GObject osztály – mely önmagában is számos hasznos funkcióval rendelkezik – minden widget, illetve számos más a GTK-ban használt nem vizuális elemnek o˝ se. Meg kell jegyezni, hogy a gtkmm, illetve a PyGObject esetén – lévén ezen esetekben az objektum-orientáltságot támogatja mag a nyelv – természetesen a származtatás nagyságrenddel egyszer˝ubb, de mintapéldákat felhasználva némi rutinnal a GTK+ esetén sem igényel különösebb er˝ofeszítést. A widgetek öröklési fájáról már itt érdemes megjegyezni, hogy az nem csupán két szint˝u. A GObject típus a fa csúcspontja, de közte és az egyes widgettípusok között adott esetben még számos csomópont található az öröklési fában. A hasonló funkcionalitású, következésképp rendszerint hasonló megjelenés˝u widgetek (9.1) értelemszer˝uen egymásból származnak. Ez még a leghétköznapibb esetekben is igaz. A számok kezelésére is alkalmas widget (spin button) o˝ se az egyszer˝u, egy sornyi szöveg befogadására alkalmas beviteli mez˝ot megvalósító widget (entry). 1a
GtkWindow típushoz tartozó függvények prefixe gtk helyett gtk_window Window típus a gtkmm esetén Gtk névtéren belül szerepl˝o Window nev˝u osztály 3 a módszer egyebek mellett pimpl (pointer to implementation idiom) néven is ismert 2a
7
(a) Egysoros beviteli mez˝o[9]
(b) Számbeviteli mez˝o[9]
2.1. ábra. Örökl˝odés hasonló funkciójú widgetek között A mechanizmus további el˝onye az interfészek4 kialakításának lehet˝osége. A GTK+ a 3-as f˝overziót megel˝oz˝oen törekedett arra, hogy a különböz˝o widgetek azonos funkciót megvalósító részeit (kattintható, szerkeszthet˝o, görgethet˝o elemek) egységes programozási felületen keresztül érhetjük el, ezt követ˝oen ez a tendencia tovább er˝osödik. Polimorfizmus Hasonlóan az örökl˝odéshez – pusztán nyelvi szinten – itt sem érhet˝o el teljes kör˝u megoldás C nyelv esetén. Ugyanakkor a f˝o momentum, vagyis a származási hierarchia egyes osztályainak specifikus viselkedése egy adott funkciót megvalósító metódusok tekintetében elérhet˝o. A widgeteket leíró struktúrákban ugyanis a származtatott osztályokból felülírhatóak az egyes funkciókat implementálható függvények mutatói5 . Az így létrejöv˝o többalakúság bár közel sem tökéletes, ám számos gyakorlati problémát megold. Ezen felül a GTK+ minden widgettípushoz – mondhatni osztályhoz – definiál egy-egy makrót, melyek segítségével futásid˝oben ellen˝orizhet˝o egy adott widget, azaz objektum tényleges típusa, hasonlóan ahhoz, amire a dynamic_cast használata ad lehet˝oséget a C++-ban. Azt a mechanizmust, melynek révén lehet˝ové válik a GTK+-ban a futás idej˝u típusellen˝orzés, a már említett GObject osztály implementálja, az ebb˝ol származó saját osztályainknak nem csupán lehetséges, de szükséges is a használata.
2.1.2.
A GTK alapfogalmai
Widget A fogalmat egyrészt, mint gy˝ujt˝ofogalmat használjuk a grafikus felhasználói felületek programozása során a felhasználó felületek egyes grafikai elemeinek megnevezésére6 , mint amilyen például egy rádiódomb, egy szövegbeviteli mez˝o, vagy akár egy kép. Másrészr˝ol a GtkWidget minden – ez el˝obbi értelemben vett widget – o˝ sosztálynak a neve is – még ha az származtatás a C esetében nyelvi szinten nem is támogatott – melyb˝ol minden egyes elem származik. A GtkWidget osztály, mint a widget fogalom objektum-orientált leképezése a felhasználó felület egyes elemeinek tulajdonságait, illetve az azokhoz kapcsolódó m˝uveleteket zárja egységbe, biztosítva egyúttal az általa implementált funkciók újrahasznosíthatóságát éppúgy, mint a felülbírálhatóságukat. Lássuk, hogy ezen általánosságban megfogalmazott elvek mögött mi is rejt˝ozik. Tulajdonságok A mindennapi felhasználás – ez esetben ugye a mindennapi szoftverfejlesztés – során talán a leggyakrabban felmerül˝o kérdés – nyilván csak azt követ˝oen, hogy megismerkedtünk milyen tulajdonságokkal bírnak a különböz˝o widgettípusok –, hogy mik ezen tulajdonságok aktuális értéki konkrét objektumaink esetén. Ezen tulajdonságok (property), illetve ezek értékei határozzák meg widgeteink megjelenését, a felhasználói interakciókkal, illetve más widgetekkel összefügg˝o viselkedését. Ezek a tulajdonságok lehetnek egészen kézenfekv˝oek, mint amilyen például egy beviteli mez˝oben szerepl˝o szöveg, egy folyamatindikátor százalékban vett értéke, egy rádiógomb be-, kikapcsolt állapota. Lehetnek teljesen általánosak, mint amilyen widgetek neve, láthatósága, méretei, a tartalmazó konténerben elfoglalt helyzetük, igazításuk. Tükrözhetnek valamilyen állapotot, mint hogy a widget fókuszban van-e, fogad-e a felhasználói interakciókat és természetesen számos – csak az adott widgettípusra vonatkozó – tulajdonságot. Szignál Lévén egy alapvet˝oen eseményvezérelt eszközr˝ol beszélünk a fent említett tulajdonságok értékeinél már csak a widget által definiált események (event) bekövetkezésér˝ol, vagy éppen elmaradásáról való értesülés lehet fontosabb. Különösen azon esetekben mikor az esemény számunkra valamilyen szempontból jelent˝oséggel bírnak, következésképp arra reflektálni szeretnénk. Ez persze nem mindig van így, hiszen mondjuk adatok bevitelére szolgáló ablakban egy rádiógomb 4a
kifejezés alatt a Java nyelv interfész, illetve a C++ absztrakt osztálya értend˝o hasonló eredményre vezet, mint a C++ virtual kulcsszavának használata esetén 6 ebben az értelemben a ”window gadget” kifejezés rövidítése 5 ami
8
állapotának változása nem feltétlenül érdekes, inkább csak annak akkori értéke, mikor az ablakon található nyugtázó gombot (pl: Ok, Alkalmaz, . . . ) lenyomtuk és az ablakban megadott adatoknak megfelel˝oen szeretnénk eljárni. Ellenben ez utóbbi eseményr˝ol – mármint hogy a gomb lenyomásra került – csaknem minden esetben értesülni szeretnénk. A widgetek eseményeinek bekövetkeztér˝ol a GObject osztály által implementált értesítési rendszer, a szignálok (signal), révén áll módunkban tudomást szerezni. Ezen mechanizmus keresztül valósítható meg az eseményekhez kezel˝ofüggvények (callback) kapcsolása, ahol a felhasználó akcióra a megfelel˝o reakciót válthatjuk ki. Az imént említett nyugtázó gomb lenyomásának hatására példának okáért bezárhatjuk az ablakot, vagy épp hibaablakot dobhatunk fel, ha a bevitt adatok a validáció során nem bizonyultak helyesnek. Itt érdemes megjegyezni, hogy a GObject nem csupán a GtkWidget osztály o˝ se, hanem más GTK-s elemeknek is, melyek jellemz˝oen nem hagyományos widgetek, ugyanakkor ki szeretnék használni a GObject osztály szolgáltatásait. Az olyan elemeknek is lehetnek tehát szignáljai, melyek nem jelennek meg közvetlenül a felületen, amire egy adattároló (pl: GtkTreeModel, TextBuffer) objektum lehet jó példa. Ezen osztályok példányai is szignálokon keresztül kommunikálnak, így adnak például jelzést arról, hogy a tárolt elemekben változás állt be. A GTK szignál kulcsszavának jelentése (jel, jelzés) tehát jól tükrözi funkcióját. Minden widgethez tartoz(hat)nak különböz˝o események – mint amilyen egy gomb esetén annak lenyomása (vagy éppen felengedése), egy beviteli mez˝onél az abba történ˝o írás – melyekr˝ol a widgetek – jellemz˝oen GDK révén, egy alacsony szint˝u7 esemény formájában – tudomást szereznek, majd elvégzik a megfelel˝o m˝uveleteket – gomb újrarajzolás, beviteli mez˝obe karakter írása a karakter megjelenítése – majd értesítést küldenek a program többi része felé, immár egy magasabb szinten8 értesítést. Ezt az értesítést, avagy jelzést nevezzük szignálnak. A szignálokhoz alapvet˝oen három tevékenységhez kapcsolódik. Ezek közül kett˝o leginkább csak a saját widgetek fejlesztése során kerül el˝o, míg a harmadik gyakorlatilag még a legegyszer˝ubb esetekben is nélkülözhetetlen. Ez utóbbi a függvények kapcsolása (connect) az eseményekhez, ezt azonban meg kell el˝ozze a a másik két említett m˝uvelet. Id˝orendi sorrendben ez a szignálok regisztrációja register) – ahol meg kell adunk az eseményünk nevét, illetve paramétereit –, illetve a szignálok küldése, kibocsátása (emit), ahol a regisztráció során megadott nevet, valamint paramétereket szükséges megadni. Az eseménykezel˝ok kapcsolásakor ezt a nevet használjuk fel, meghívásukkor pedig ezek a paramétereket kapjuk meg. Callback Amennyiben egy adott widgethez kapcsolódó valamilyen eseményr˝ol (event) tudomást kívánunk szerezni a program futása során, ezt úgy tehetjük meg, hogy a widget megfelel˝o típusú jelzéséhez (signal) eseménykezel˝o függvényt (callback) kapcsolunk. Itt minden olyan m˝uvelet elvégezhet˝o, ami nem a widgethez, hanem annak programunkban betöltött szerepéhez köt˝odik. A korábbi példánál maradva ha egy adatok bevitelére szolgáló ablak Ok gombjának lenyomásánál szükséges lehet a felhasználó által megadott adatok szintaktikai, illetve szemantikai ellen˝orzése, az ellen˝orzött adatok mentésére, majd az ablak bezárására, probléma esetén hibaablak feldobására, valamint a beviteli folyamat újrakezdésére, akkor azt a kezel˝o függvényben mind megtehetjük. Egyébiránt az egyes tulajdonságok (property) megváltozása szintén események min˝osül, vagyis ezekhez is módunk van függvényeket csatolnunk.
2.1.3.
A GTK+ muködési ˝ sajátosságai
Main Loop Az események kezelése kapcsán megválaszolandó az a triviálisan adódó kérdés, hogy az egyes widgetek hogyan szereznek tudomást a rajtuk – a felhasználók, vagy éppen az automata tesztel˝o eszközök által – végrehajtott akciókról. A válasz pedig épp ugyanabban rejlik, mint bármely más felhasználói felületek fejlesztésére szolgáló eszközkészlet esetén, azaz az eseményvezérelt m˝uködési modellben. Ez nem jelent más mint, hogy a GTK+ mindaddig várakozik, amíg valamilyen forrásból esemény nem érkezik (pl: egér mozgatása, gombjainak lenyomása, billenty˝u felengedése, . . . ). Amennyiben egy esemény bekövetkezik meghatározza, melyik widgetet érintett az esemény által, meghívja a widget megfelel˝o eseménykezel˝o függvényét, majd újabb várakozásba kezd. Ennek a várakozási ciklusnak az implementációja main loop. A GTK+ tulajdonképpeni f˝ociklusa a Glib függvénykönyvtárban implementált, a GTK+ ezt újra felhasználva ciklizál, várva a grafikus szerver – legyen az a Linux, a Mac OS X, vagy a Windows megfelel˝o alrendszere – üzeneteire. Mindezt a GDK-n keresztül teszi, mely – mint azt az el˝oz˝o részben is említettük – egy vékony burkoló réteg az ablakozó rendszer köré. A main loop tehát az ami az imént említett kapcsolaton át eljuttatja az ablakozó rendszer alacsony szint˝u eseményeit a GDK által standardizált formában az egyes widgetekhez, hogy ezek a feldolgozást követ˝oen egy magasabb szint˝u eseményt váltsanak ki a többi widget, illetve az applikáció más részei felé. 7 billenty˝ u 8 beviteli
lenyomása, felengedése, egérkattintás, . . . mez˝o értékének változása, kattintás, . . .
9
Referencia-számlálás A GTK+ segítségével létrehozott felületek – ahogy azt a kés˝obbiekben látni fogjuk – nem widgetek szórvány halmazát, hanem egymással szoros összefüggésben álló (szül˝o-gyerek, modell-nézet-vezérl˝o kapcsolat) elemek hálózatát jelentik. Ennek okán az megoldandó feladat, hogy az egymáshoz valamilyen szempont alapján köt˝od˝o elemek egymás oly módon tudják hivatkozni, hogy hivatkozások létrejötte, illetve megsz˝unése egyúttal a widgetek memória menedzsmentjére is megoldást adjon. Ennek bevett módszere a referenciák tartása a hivatkozott elemekre, mellyel a GTK+ is él. Minden GObjectb˝ol származó osztály – így a GtkWidget is – rendelkezik referencia-számmal, mely tulajdonképpen azt fejezi ki, hogy hányan hivatkoznak az adott elemre. A GTK+ – pontosabban ez esetben a GLib – ”lebeg˝o” referenciát (”floating” reference) alkalmaz, mely azt jelenti, hogy az objektum létrejöttekor annak referenciája 1 lesz, bár a widgetre ekkor még nem hivatkozik semelyik másik widget sem, azaz ezt a referenciát úgymond nem birtokolja senki. Amikor egy widgetre megszületik ez els˝o valódi hivatkozás, például egy konténer osztályba – mint amilyen egy közönséges ablak is – tesszük a widgetet, vagyis létrejön az els˝o valódi hivatkozás az elemre, akkor az a hivatkozó birtokába kerül. A referencia-érték változatlanul 1 marad, viszont a lebeg˝o referencia elsüllyesztésre (sink) kerül. Minden ezt követ˝o esetben a konténerb˝ol történ˝o eltávolítás csökkenti, ahhoz való hozzáadás pedig növeli a referencia értékét. Érdemes felhívni a figyelmet arra, hogy az elmondottak alapján, ha hozzáadtuk widgetünket egy konténerhez, majd pedig eltávolítjuk bel˝ole azt, akkor annak referenciája 0-ra csökken, ami maga után vonja a widget destruktorának lefutását. Ezt elkerülhetjük, ha az eltávolítás el˝ott explicit módon növeljük a referenciát, amit aztán csökkentenünk kell, ha egy másik osztály „birtokába” adjuk a widgetet. Szül˝o-gyerek kapcsolat A szül˝o-gyerek kapcsolat a már említett konténerek – azaz a GtkContainer, illetve az abból származó osztályok – viszonylatában merül fel. Ezen elemek teszik lehet˝ové a widgetek felületen való elrendezését (ablakok, táblázatok, gombok, . . . ), egymásba ágyazását. A konténer tehát az a widget, amely további widgetet, vagy widgeteket tartalmazhatnak. Ilyen értelemben egy szül˝o-gyerek kapcsolatot valósítanak meg, ahol minden szül˝onek lehetnek gyermekei, de egy gyermek widgetnek minden esetben csak egy szül˝oje van, vagyis a szül˝ok és a gyerekek egy fa hierarchiát alkotnak. Ez a szerkezet több szempontból is fontos szerepet játszik a GTK+ m˝uködése során. Egyrészr˝ol a referencia-számlálásnál már említett módon, azaz ha egy widgetet hozzáadunk egy konténerhez, akkor az úgymond tart rá egy referenciát – vagy a referenciaszám növelésével, vagy ”lebeg˝o” referencia elsüllyesztésével –, majd elereszti azt a konténerb˝ol való eltávolításakor. Másrészr˝ol egy még nem ismertetett – a szül˝o- és gyerekwidgetek viszonyának tulajdonságait rögzít˝o – mechanizmust tesz lehet˝ové, melyr˝ol a kés˝obbiekben még részletesebben esik szó. Elöljáróban csak annyit, a widgetek saját tulajdonságain (property) túl, léteznek olyanok is melyek szül˝o widgetekkel való kapcsolatára jellemz˝oek, mint például a gyerek widgetek elhelyezkedése a konténerben (pozíció, térköz, kiterjedés, . . . ). Interfészek A GTK+, er˝osítve az objektum-orientált megközelítést, olyan absztrakciós rétegeket definiál, amiket az adott funkciót (szöveg bevitel, aktiválhatóság, igazítás) betölt˝o widgetek implementálnak. Az ilyen típusú általánosítások komoly haszonnal bírnak, mikor az adott funkciót egységes felületen keresztül, a konkrét implementáció részleteivel nem tör˝odve, szeretnénk kezelni. Függetlenül attól, hogy például egy, vagy többsoros beviteli mez˝or˝ol legyen szó, a karaktereket épp úgy szeretnénk kiolvasni mindkét esetben. Éppúgy igaz ez az elrendezés (orientation) tekintetében, hisz amennyiben a megfelel˝o widgetek – jellemz˝oen a konténerek – megvalósítják ezt, az elrendezésre vonatkozó interfészt, a vízszintes, illetve függ˝oleges orientáció futás közben is könnyedén váltható (flip). Van egy olyan interfész, amit minden egyes felülettel rendelkez˝o widget (GtkWidget) és számos felülettel nem rendelkez˝o objektum (GObject) is implementál (GtkBuildable). Ezen osztályon keresztül valósulnak meg a legalapvet˝obb funkciók, mint amilyenaz objektumok nevének, tulajdonságok értékének lekérdezése, beállítása, a gyerek widgetek létrehozása9 .
2.2.
A tesztelés fogalmai
A tesztelési feladatok ellátása kapcsán a GTK ismerete bizonyos esetekben nem árt, míg más esetekben nem sokat használ. Ez azon egyszer˝u oknál fogva van így, mivel a tesztelés során a megközelítés mer˝oben eltér˝o, lévén a teszteléshez használt rendszer nem közvetlenül a GTK+-ra , hanem az ATK-ra épít.
2.2.1.
Az ATK koncepciója
A már említett ATK koncepciójában némiképpen különbözik a GTK+-t˝ol. Lévén ezt az interfészt a fogyatékossággal el˝o emberek szükségleteinek kielégítésére tervezték, els˝osorban nem magukra a widgetekre, vagy azok kapcsolataira, felületi 9 mára
a Glade elnevezés˝u felhasználói felületek tervezésére szolgáló alkalmazás is ezt az interfészt használja
10
megjelenésére, hanem az általuk hordozott információkra koncentrál. Ezen információk rejt˝ozhetnek természetesen a widgetek által megjelenített szövegekben, vagy számszer˝u értékekben éppúgy, mint a widgetek aktuális állapotában (aktív, szerkeszthet˝o, látható, . . . ), ugyanakkor persze az egymás közi viszonyok (tartalmazás, vezérlés, . . . ) is meghatározóak lehetnek. Ezen interfészek, állapotok, illetve viszonyok függetlenek a konkrét implementációtól. Bármely widgetkészlet számára implementálhatóak, s˝ot implementálandóak, ami annyit tesz, hogy az egyes widgetkészletek logikája még ha nagyjából egyezik is az ATK logikájával, számos ponton kisebb-nagyobb eltérések tapasztalhatóak, amiket szükséges valamilyen, a widgetkészlet és az ATK között elhelyezked˝o, azokat összeköt˝o (bridge) implementációval áthidalni. Interfészek A kifejezetten a funkcionalitáson alapuló megközelítés egyenes következményei az ATK interfészei. Ezek gyakorlatilag a grafikus felhasználói felület elemeit legf˝obb funkcióik szerint csoportosító eszközök. Egy adott GTK widget természetesen megvalósíthat több interfész is, lévén többféle funkciót is betölthet. Egy egyszer˝u példával megvilágítva a helyzetet egy közönséges gomb (GtkButton) egyszerre valósítja meg a szöveg (AtkText), illetve a képek (AtkImage) lekérdezésére szolgáló interfészeket, hiszen a gombon kép és felirat egyaránt elhelyezhet˝o. Állapotok Bizonyos esetekben nem a widget által tartalmazott adat – legyen az szám, vagy szöveg, esetleg kép – hordozza az információt, hanem widget valamilyen szempont szerinti állapota. Általánosságban véve ilyen állapot lehet egy widget láthatósága, egy ablak átméretezhet˝osége, egy beviteli mez˝o szerkeszthet˝osége, vagy akár egy rádiógomb aktív mivolta. Az állapotok mindegyike bináris, azaz igaz vagy hamis értékkel írható le. Ennek megfelel˝oen az ATK által definiált állapotok egy bithalmazt alkotnak, melyek leírják egy adott widget konkrét id˝opillanatban vett állapotát. Viszonyok Tesztelési szempontokból létezik még egy jelent˝oséggel bíró tulajdonságtípus, mely azonban nem konkrét widgetek paramétereit, hanem azok egymáshoz köt˝od˝o viszonyát írják le. Úgyis, mint egy felirat (label) és a hozzá köt˝od˝o widget összetartozását, a vezérl˝o és vezérelt widget között fennálló viszonyt, vagy éppen a fák megjelenítésére használt widget esetén a az elemek szül˝o-gyerek kapcsolatait. Az egyes viszonyok (relation) – hasonlóan az imént említett állapotokhoz – szintén két értékeket vehetnek fel, bár ellentétben azokkal az állapotokkal az egyes viszonyok csak egy másik widgettel együtt van értelmük. Az ellentétes el˝ojel˝u viszonyok, mint a vezérl˝o és vezérelt widget egyidej˝uleg is igazak lehetnek, más-más widgetekkel összefüggésben.
2.2.2.
A Dogtail muködése ˝
A Dogtail a szoftverek akadálymentesítésének megvalósítására használta technológiai módszereket (assistive technologies) alkalmazza a tesztelend˝o alkalmazások vezérlésére.
2.2. ábra. Akadálymentesített szoftverek elérése[1]
11
A m˝uködés modell (2.2 ábra) végeredményben nem túl bonyolult. Az applikáció közvetlenül nem szólítható meg. Ahogy ezt hang alapú vezérlést megvalósító, illetve képerny˝oolvasó szoftverek is teszik, az AT SPI-n (Assistive Technologies Service Provider Interface) keresztül szólítják meg a megfelel˝o szoftvert. Amennyiben ez egy GTK+ felhasználásával fejlesztett alkalmazás, akkor a kérésre a Gail (1.4.1) nev˝u alrendszert futtatva válaszol, az ATK interfészben leírtaknak megfelel˝oen. Ez a mechanizmus adja az alapját az automata tesztelésnek is. Maga a teszt is az AT-SPI interfészt használja arra, hogy a tesztelend˝o applikációval kommunikáljon, ezen keresztül kérdezi le a korábban említett állapotokat, viszonyokat, illetve szólítja meg az egyes widgetek által implementált interfészeket. Az így vezérelés alá vont szoftver egyes elemeinek állapotát, illetve tulajdonságait követve vonhatóak le következtetések arra nézvést, hogy az adott szoftver a kívánalmaknak megfelel˝oen m˝uködik-e.
12
3. fejezet
A fejlesztés menete 3.1. Gimp Tool Kit 3.1.1.
Beszerzése
A GTK+ beszerzésére alapvet˝oen két módszer kínálkozik. Az egyik megoldás, hogy hagyatkozunk az általunk használt operációs rendszerre és az általa biztosított, vagy legalábbis arra elérhet˝o változatot telepítjük. A másik lehet˝oség, hogy letöltjük a GTK+ és a függ˝oségek forráskódját és némi nehézséget vállalva ezeket fordítjuk le. El˝obbi eset b˝oségesen megfelel amennyiben még csak most ismerkedünk a GTK+ függvénykönyvtárral, illetve nem akarunk túlságosan belebonyolódni az grafikus alkalmazások fejlesztésébe. Elkerülhetetlenül az utóbbit kell azonban választanunk, ha az átlagnál jobban el szeretnénk merülni a GTK+ rejtelmeiben, ha esetleg hibákat javítanánk, vagy változásokat eszközölnénk magán a grafikus eszközkészleten. Bináris változat A GTK változatok beszerzése nem jelent különösebb feladatot, amennyiben valamelyik népszer˝u Linux disztribúciót használjuk, hiszen azok nagy valószín˝uséggel már amúgy is telepítve vannak az általunk használt rendszeren, lévén vélhet˝oleg már használunk ezen eszközök segítségével fejlesztett szoftvereket. A fejlesztéshez, illetve teszteléshez a bináris változatokon túl fejleszt˝oi csomagokra is szükséges lesz, amit rpm, illetve deb alapú disztribúciók esetén rendre az alábbi parancsok kiadásával tehetünk meg: sudo yum install gtk-devel-package sudo apt-get install gtk-dev-package
GTK+. A GTK+ fejlécfájlokat és egyéb állományokat – melyekr˝ol a kés˝obbiekben (3.2.1) még részletesebben is esik szó – a Debian/Ubuntu, illetve Fedora rendszereken a libgtk-3-dev, illetve a gtk3-devel csomagok tartalmazzák. gtkmm. A fenti csomagok természetesen csak a C nyelv˝u változat – azaz a GTK+ – használatához elegend˝oek, amennyiben a C++ nyelvet – ezzel együtt a gtkmm függvénykönyvtárat – kívánjuk használni, további csomagokra (libgtkmm-3.0-dev, vagy gtkmm3-devel) is szert kell tennünk. PyGObject. Amennyiben a GTK alapú fejlesztéssel a Python nyelv révén ismerkednénk a már említett fordított nyelvek helyett, akkor a fenti csomagokat nem, a python-gi, vagy a python-gobject csomagokat viszont be kell szereznünk. Dogtail. Az automata tesztek készítéséhez szükséges Python függvénykönyvtár – a Dogtail – állományait az azonos nev˝u csomag tartalmazza, melyek installálása a fentiekhez hasonlóan történik. Forráskód A GTK forrásának beszerzésére szintén több módszer kínálkozik. Egyrészr˝ol az általunk használt Linux disztribúció biztosít eszközöket forráscsomagok telepítésére. A korábbi deb, illetve rpm alapú rendszerek példájánál maradva ez rendre az alábbiak szerint történik. apt-get source gtk-src-package yumloader --source gtk-src-package
Lehet˝oség van természetesen az egyes verziók letöltésére a GNOME projekt weboldaláról is, 13
wget http://ftp.gnome.org/pub/gnome/sources/gtk+/major.minor/gtk+-major.minor.micro.tar.gz wget http://ftp.gnome.org/pub/gnome/sources/gtk+/major.minor/gtk+-major.minor.micro.tar.bz2
valamint használhatóak e célra egyes projektek verziókel˝oi is, ha szeretnénk mindig az aktuális forráskóddal dolgozni. git clone git://git.gnome.org/gtk+
Ha azonban magunk szeretnénk a teljes GTK-t fordítani – legyen szó a C, vagy a C++ nyelv˝u változatról – számolnunk kell azzal, hogy számos egyéb komponens (GLib, Pango, Cairo, ATK, . . . ) fordítására, illetve az frissítéseket követ˝o újrafordítására válik szükségessé, ami meglehet˝osen id˝oigényes és fáradságos feladat, amit a Linux disztribúción összeállítói már megtettek helyettünk. Így célszer˝u kezdetben ezt kihasználni és a saját fordításba csak akkor belekezdeni, ha arra feltétlenül szükségünk.
3.1.2.
Fordítása
Amennyiben a korábban említett nehézségek ellenére mégis nekivágunk a GTK saját fordításának, akkor sem vagyunk magunkra hagyva. A GNOME projekt része egy JHBuild elnevezés˝u szoftver1 , amit a GNOME projekt moduljaiból összeállított halmazok – mint amilyen a GTK és annak függ˝oségei – letöltésére, frissítésére, fordítására és fordítás eredményeként létrejött futtatási környezetben való munkára használhatunk. Mindenekel˝ott azonban szükségünk lesz néhány olyan eszközre, amik a Linux alapú rendszereken fordítási feladatok ellátására kvázi szabványnak számítanak. A GNOME – sok más fejlesztési projekthez hasonlóan – az Autotoolst2 használja moduljainak fordításához. Ennek részleteibe nem célunk ezen dokumentum keretében elmerülni, már csak azért sem, mert a JHBuild ezen, fordításhoz szükséges, függ˝oségek telepítését megoldja helyettünk. jhbuild sanitycheck jhbuild bootstrap
Az parancs futtatásának hatására ellen˝orzésre kerül a konfigurációban megadott könyvtárak írhatósága, a szükséges fordítási eszközök telepített mivolta. Ezt követ˝oen kerülhet sor a forráskódok letöltésére, a fordítás el˝otti konfigurálásra, magára fordításra, valamint az elkészült bináris állományok telepítésére. Ehhez a következ˝o parancsokat használhatjuk. jhbuild update jhbuild make
A fordítás végeztével lehet˝oségünk van az újólag létrejött környezetbe úgymond belépni, vagy közvetlenül parancsokat futtatni. Ez gyakorlatilag ennyit tesz, hogy számos környezeti változó beállításának eredményeként saját a fordítandó alkalmazásaink és a fordítás eredményeként létrejött futtatandó állományok is a az új környezetben létrehozott függvénykönyvtárakat fogják használni a rendszeren található változatuk helyett. jhbuild run jhbuild shell
Ennek akkor van leginkább haszna, ha szeretnénk az általunk fejlesztett alkalmazást a rendszeren elérhet˝o GTK változaton kívül a legfrissebb verzión is kipróbálni, GTK valamely modulján szeretnénk változtatni és ennek hatását látni alkalmazásunkra, vagy éppen csak nyomon követnénk a GTK változásaik, aktuális fejlesztéseit még miel˝ott azok az általunk használt disztribúcióban is megjelenik.
3.2.
Saját alkalmazások
Mivel a továbbiakban részletesen foglalkozunk majd a GTK alapú alkalmazások létrehozásával, itt most csak a legfontosabb parancsokat vesszük számba, melyek révén a C, illetve C++ nyelv˝u forrásfájlokból futtatható bináris állományokat hozhatunk létre.
3.2.1.
Fordítás és linkelés
Akár a rendszeren található, akár JHBuild, vagy más eszköz révén létrehozott környezetben lév˝o GTK változatot is használunk, az alábbi parancssorok segítségével fordíthatóak le forrásfájljaink. gcc gtk_sourcefile.c -o gtk_binary ‘pkg-config --cflags --libs gtk+-3.0‘ g++ gtkmm_sourcefile.cc -o gtkmm_binary ‘pkg-config --cflags --libs gtkmm-3.0‘ 1 telepítése 2a
a korábbiaknak leírtaknak megfelel˝on történik GNU fordítási rendszere, mely tulajdonképpen az Automake, Autoconf, Libtool együttese.
14
Ahhoz, hogy a GTK+, illetve gtkmm fordítási függ˝oségeit ne magunknak kelljen megadnunk a pkg-config parancsot hívhatjuk segítségül, hogy a GCC részére a megfelel˝o paramétereket meg tudjuk adni. A -cflags paraméter hatására a fordításhoz, míg a -libs eredményeképp a linkeléshez szükséges opciókat kapjuk vissza. A parancs két ` (backtick) közé zárt. aminek hatására a program kimenete része lesz a fordító parancssorának, amivel pont az kívánt hatást érjük el.
3.2.2.
Futtatás
Ezek után már csak az örömteli pillanat van hátra, mikor a két különböz˝o nyelven és függvénykönyvtárral lekódolt teljesen azonos funkciójú programunkat lefuttatjuk a ./gtk_binary, illetve a ./gtkmm_binary paranccsal. Amennyiben a Python nyelv˝u változat mellett tesszük le voksunkat a fordítás, mint lépés kimarad, a futtatás történhet közvetlenül (./gtk_script.py), amennyiben van az adott fájlon futtatási jog, vagy a Python interpreternek paraméterként (python gtk_sctipt.py). A továbbiakban ezt az utóbbi sémát követjük. Ezzel túl is vagyunk azon a rövid áttekintésen ami után már épp ideje nekilátnunk els˝o ablakunk implementálásának, futtatásának és tesztelésének.
15
4. fejezet
Els˝o ablakunk 4.1.
Kódolási alapismeretek
A nagyobb nyílt forrású projektek a kódolás, kódszervezés során egy meghatározott konvenciót követnek, bár egy olyan méret˝u projekt esetén, mint a GNOME az egyes részterületeken lehetnek eltérések, azzal együtt is, hogy az azonosságok nyilván er˝os többségben vannak. Lássuk mik ezek a GNOME, illetve a GTK projektek esetén.
4.1.1.
Forráskód formázása
A GTK fejleszt˝oi a GNU coding standard[2], illetve a Linux kernel coding style[3] irányelveit alkalmazzák, ami mindaddig csak az olvasást, megértését el˝osegít˝o módszertani eszköz, amíg nem áll szándékunkban a GTK fejlesztésébe, javításába belefogni, ugyanakkor két oknál fogva mégis érdemes megemlíteni. Ha még nem ismerkedtünk meg egyetlen kódolási konvencióval sem, akkor az említett kett˝o – mind népszer˝uségük, mind letisztult mivoltuk okán – alkalmas választás lehet. A másik ok, hogy néhány a fejlesztés során hasznos információ ezen konvenciókból következik.
4.1.2.
Elnevezési konvenciók
A GNOME projekten belül nem csak formázási, de elnevezési konvenciók is használatosak. Ezek az egyes funkciót megvalósító szoftverelemekre (pl: függvények, makrók, . . . ) vonatkoznak, amik közül a az alábbiak már a legegyszer˝ubb példák esetén is felt˝unnek. • az egyes nevek részekre oszthatóak, • a részek meghatározott sorrendben követik egymást, ahol – kezdve a GNOME megfelel˝o projektjével (pl: atk, gtk, . . . ), – folytatva a vonatkozó osztály nevével (pl: entry, label, . . . ), – befejezve a megvalósított m˝uvelettel (pl: get, set, . . . ), – illetve a m˝uvelet tárgyával (pl: text, value, . . . ), • a részeket aláhúzásjel (’_’) választja el, • az egyes részek rendszerint vagy csak kis-, vagy csak nagybet˝uket, illetve számokat tartalmaznak. Fentieknek megfelel˝oen egy – a GTK+ által implementált – rádiógomb aktív mivoltát lekérdez˝o függvény neve a gtk el˝otaggal kezd˝odik, amit a osztálynév, vagyis a radio_button, majd lekérdezésr˝ol lévén szó get akciónév követ, végül pedig a tulajdonság neve (actvie) zár. Aláhúzás jelekkel összef˝uzve gtk_radio_button_get_active. A C++, illetve a Python nyelv˝u változatok esetén is hasonló az elnevezés módszertana a nyelvb˝ol fakadó sajátosságok okozta eltéréssel természetesen. A gtkmm esetén a projektek nevét tartalmazó prefixum szerepét a Gtk névtér veszi át, míg az osztályok neve a C++ osztályok neve lesz. A tagfüggvények az el˝obbi két el˝otag (pl: gtk_entry) nélküli nevek lesznek (pl: set_text). A PyGobject esetén a névtér szerepét a Gtk modulnév veszi át, a Python osztályok nevei ugyanazt a szerepet töltik be, mint a C++ esetén.
16
4.1.3.
Fejlécfájlok és importálás
Szakítva az el˝oz˝o f˝overziónál (2.x) megszokottaktól az új f˝overzió (3.x) esetén, a C, illetve a C++ változat egyaránt csak egyetlen állomány beszerkesztésére (include) van lehet˝oség és szükség. A korábbiakban az egyes widgetekhez tartozó fejlécállományok (pl: gtk/gtkentry.h) beszerkesztésére külön-külön volt lehet˝oség függ˝oen attól, melyekre van, illetve melyekre nincs szükségünk. Most azonban közvetlenül csak a gtk/gtk.h szerkeszthet˝o be, a többi fejlécállomány esetén hibaüzenetet kapunk. Így mind a C, mind a C++ változat azonos módon m˝uködik, ami egyébiránt hasonlít a Python modul importjára.
4.2.
Minimálisan alkalmazás
Ennyi bevezet˝o után lássuk egymás mellett (4.1. Kódrészlet) a három nyelvi változat (C, C++, illetve Python) kódját számba véve azok hasonlóságait és különböz˝oségeit. Ami talán els˝o látásra is felt˝un˝o, hogy messze a GTK+ változat ”kóds˝ur˝usége” a legnagyobb, vagyis a C nyelv˝u változat igényli ugyanazon funkcionalitás mellett a legtöbb kódsor (18 sor) begépelését, és egyben a legtöbb munkát is. Ez persze nem jelenthet különösebb meglepetést ha van némi tapasztalatunk az interpretált, illetve a fordított, a procedurális, illetve az objektum központú nyelvek esetén elérhet˝o fejlesztési sebesség terén.
4.2.1.
Forráskód
Els˝o közelítésben a már említett formai különbségek lehetnek szembeötl˝oek, ugyanakkor számos, a tartalmat, megvalósítást érint˝o eltérés is felfedezhet˝o ebben a még oly kevést kódsort tartalmazó példában. Ezek megértése nagyban könnyíti az egyes nyelvi változatok közötti átjárást és ne utolsó sorban a GTK koncepcionális sajátosságaira is rávilágít. Lássuk tehát sorról sorra az imént olvasott kódok magyarázatát. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#include
#include
from gi.repositoryimportGtk
int main(int argc, char *argv[]) { GtkWidget *window;
int main(int argc, char *argv[]) {
if __name__ == "__main__":
}
gtk_init (&argc, &argv);
Gtk::Main kit(argc, argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect(G_OBJECT(window), "delete-event", G_CALLBACK(gtk_main_quit), NULL); gtk_widget_show (window);
Gtk::Windowwindow; window.show();
window = Gtk.Window() window.connect("delete-event", Gtk.main_quit) window.show()
gtk_main();
Gtk::Main::run();
Gtk.main()
return 0;
return 0; }
Kódrészlet 4.1. Minimálisan szükséges kód GTK+, gtkmm, illetve PyGobject használata mellett 1. sor A header fájlok beszerkesztésének különböz˝oségeir˝ol az el˝oz˝oekben esett szó, így erre itt csak azt megemlítend˝o térünk ki, hogy a Python változat import parancsának azt a változatát alkalmazzuk, ami a leger˝osebb hasonlóságot eredményezi a másik két nyelvi változattal, már ami a gtk kulcsszó forráskódban történ˝o megjelenéseinek helyét illeti. 4. sor Ez a sor a programjaink belépési pontja, azaz itt kezd˝odik meg a futtatás, már legalábbis ami a C/C++ változatot illeti. A Python nyelv esetén az import parancs már végrehajtásra került mire ide jutunk, s˝ot erre a kódsorra voltaképpen nincs is feltétlenül szükséges[4, fej. 27.4.], leginkább csak a másik két példához való még er˝osebb hasonlóság végett került be ez a kódsor. Fontossá a main függvények tulajdonképpen csak a parancssori paraméterek GTK-nak történ˝o átadás8 szempontjából válnak. 6. sor A C nyelvi verzióban kénytelenek vagyunk blokk elején deklarálni azt a változót – ami ez esetben widgetünk címét tartalmazza majd – mivel az ISO C90 szabvány még nem, majd csak az ISO C99 támogatja a blokkon belül kifejezés után elhelyezett változó deklarációkat. Ezt viszont sem 3.0-nál korábbi GCC, sem pedig a Microsoft Visual Studio C fordítója nem támogatja, vagyis problémába ütköznénk a C++ példában használt módszerrel, így a biztonság kedvéért maradunk a hagyományoknál, azaz lokális változók deklarációja csak blokkok kezdetén szerepel. 8. sor Eljutottunk végre az els˝o GTK specifikus híváshoz, mely a Python változatból teljesen hiányzik, míg a C, illetve a C++ verzióban funkciójuk azonos, mégis van köztük egy árnyalatnyi különbség. A GTK+ esetén az argc, valamint az argv változók címeit adjuk át, biztosítandó, hogy az init függvény a GTK saját paramétereit 1 el tudja távolítani a tömbb˝ol és azok számával csökkenteni tudja argc értékét. Erre a C++-os változat esetén erre azért nincs szükség, 1a
GTK által értelmezett parancssori paramétereket összefoglalója itt olvassható.
17
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
mert még ha nem is látszik, mindkét változóra referencia adódik át a Gtk::Main konstruktorának. A Python változat esetén nincs init jelleg˝u függvényhívás, mivel az inicializálás a modul (gi.repository.Gtk) importálásával implicit módon megtörténik, másrészr˝ol a parancssori paraméterek a sys modulon keresztül bárhol hozzáférhet˝oek, azok paraméterként való átadása így felesleges. 10. sor Els˝o widgetünk létrehozása a már említett nevezéktan szerinti függvények meghívásával történik. Minden widgettípushoz létezik egy, mondjuk úgy konstruktor, melyet meghívva egy új – az adott típushoz tartozó – widgetet kapunk vissza. A hívás mikéntje természetesen függ a nyelvt˝ol magától, illetve attól is, hogy a nyelv esetén használható-e, illetve használjuk-e az objektumok-orientált megközelítést. A C nyelvi változat esetén a gtk_widgettípusnév_new forma használatos, addig a C++ esetében a prefixek szerepét a névterek veszik át, tehát általános formában a Gtk::WidgetTípusNév::WidgetTípusNév írható le a konstruktor. A Python szintén névterekkel operál, ahol azok határait a modulok jelentik, melyek neveit egymástól, illetve függvényeikt˝ol pont (.) választja el, vagyis a konstruktor Gtk.WidgetTípusNév formában írható le. A különbség nem is annyira a nevekben, mint inkább a memória kezelésében rejlik, hiszen egy újólag létrehozott objektum felszabadításáról C esetén magunknak kell gondoskodnunk, míg a C++ a t˝ole megszokott módon felszabadítja a lokális változókat. Ugyanakkor érdemes itt visszautalni a korábbiakban már említett referencia számlálásra, illetve a ”lebeg˝o” referenciára, melyek révén a különböz˝o nyelvi változat esetén mód van arra, hogy csak a legfels˝o szint˝u elemr˝ol kelljen ebben a tekintetben magunknak gondoskodnunk, a GTK a többi elem memóriakezelését maga menedzseli. Az ablak létrehozásában meg egy különbség fedezhet˝o fel a C, illetve a másik két változat között.Ez pedig a paraméterkezelés mikéntje. El˝obbi esetben meg kell mondanunk az ablak típusát (toplevel) – hiszen a C nyelv nem tesz lehet˝ové a paraméterek esetén alapértelmezett értékét –, míg a másik két esetben erre nincs szükség. Ez a paraméter ugyan mindkét esetben létezik alapértelmezett értékük, pont az, amit a C változat esetén használunk. 11. sor Anélkül, hogy a szignálok kezelésének rejtelmeiben elmerülnénk egy gondolatnyi kitér˝ot érdemes ezen kódsor a kapcsán tenni. Els˝oként azt érdemes tisztázni mire szolgál a delete-event szignál. Ez a szignál akkor váltódik ki, amikor az ablakunk felhasználó interakció hatására záródik be. Ez többféle interakciót is jelenthet – operációs rendszert˝ol és ablakkezel˝ot˝ol függ˝oen –, de alapvet˝oen az ablak jobb fels˝o sarkában2 lév˝o X gomb, vagy az Alt+F4 billenty˝uk lenyomására kell gondolnunk. A C, illetve a Python nyelv˝u változat ezen esemény bekövetkeztekor a GTK f˝ociklusát szeretné leállítani, ami annak révén ér el, hogy a delete-event szignálra a nyelvi változatnak megfelel˝o main_quit függvényt köti fel. Figyelembe véve, hogy a delete-event alapértelmezett szignálkezel˝oje megszünteti (destroy) az ablakot, ez az eljárás logikus, lévén egy programnak f˝oablak nélkül nincs igazán sok értelme. A C++ változat viszont ugyanebben a tekintetben látszólag semmilyen lépést sem tesz, ugyanakkor megfigyelhetjük, hogy néhány sorral lejjebb (15. sor) a run függvények paraméterként átadja azt ablakot, ami nagyon hasonló eredményre vezet. Egészen pontosan nem csak az átadott ablak megsz˝unésekor, de az eltüntetésekor (hide) is ki fog lépni a f˝ociklus. 13. sor Ezek után nem érhet meglepetésként bennünket, hogy miért nincs szükség a C++ változat esetén az megjelenít˝o függvény meghívására, ezt az átadott ablakra a f˝ociklust elindító run függvény megteszi, ami logikus is hiszen ha nincs egyetlen látható ablakunk sem, akkor nehéz olyan felhasználó interakció kezdeményezni – legalábbis a felhasználói felületen keresztül –, ami a f˝ociklus kilépését eredményezné. 15. sor A hívások a – korábban már részletezett – GTK main loopot indítják, azaz itt kezd˝odik meg az az eseményvezérelt szakasz, mely a választott nyelvt˝ol függ˝oen a gtk_main_quit, a Gtk::Main::quit, vagy a Gtk.main_quit meghívásáig tart. Ezekben a minimális példákban erre az egyetlen mód a futtatáskor megjelen˝o ablak bezárása, hiszen az imént említett függvényeket az ennek hatására kiváltódó delete-event szignálhoz rendeltük. Miután a szignált ”kezel˝o” függvény lefutott a GTK f˝ociklusa (main loop) kilép, azaz a run függvény futása befejez˝odik, a program futtatása az azt követ˝o soron folytatódhat. 17. sor Visszatérési értékünk mindhárom esetben 0, amit a rendelkezésre álló nyelvi módszerek legegyszer˝ubbikével érünk el, ezzel jelezvén a hívó félnek, hogy a futás rendben lezajlott.
4.2.2.
Fordítás és futtatás
A korábbiakban már említett metodika mellet a mostani forráskódok3 fordítása a követlez˝oképp néz ki: gcc gtk_minimal.c -o gtk_minimal ‘pkg-config --cflags --libs gtk+-3.0‘ g++ gtkmm_minimal.cc -o gtkmm_minimal ‘pkg-config --cflags --libs gtkmm-3.0‘
A futtatás tekintetében próbálkozzunk a ./gtk_minimal, illetve a ./gtkmm_minimal, illetve a python3 gtk_minimal.py parancsokkal abban a könyvtárban, ahol a forrásállományaink is találhatóak. 2 Mac
OS X, illetve az újabb Ubuntu verziók esetén bal fels˝o sarok csak a C, illetve a C++ változat fordítandó a Python nem
3 természetesen
18
4.2.3.
Eredmény
Nagy meglepetést nem várhatunk egy ilyen méret˝u applikációtól, viszont az azért kell tudnunk értékelni, hogy a legrosszabb esetben is alig másfél tucat kódsorból egy m˝uköd˝o grafikus felhasználó felületet lehet létrehozni, melynek eredménye az alábbi képeken látható.
4.1. ábra. Minimális mintapéldák képerny˝oképi C, C++, Python változat Tulajdonképpen csak egy puszta ablakot kapunk, bármilyen gomb, vagy egyéb elem nélkül. Minden díszítés – az ablak fejléce, a címsor szövege, a minimalizáló, maximalizáló és a bezáró gombok – egyaránt az ablakkezel˝onek és nem a GTK-nak köszönhet˝oek. Ez utóbbi gombhoz köt˝odik az egyetlen – GTK szempontjából is érdemleges4 – m˝uvelet, ami a már több ízben is említett delete-event szignált fogja kiváltani, ami a végs˝o soron a program futásának befejezéséhez vezet.
4.3.
Tesztelés
Els˝ore talán azt gondolhatjuk, hogy a fenti ablakon igazán nincs mit tesztelni, egy feladat mégis akad, mégpedig az, hogy a futó applikációt, illetve annak egyetlen ablakát megtaláljuk, majd bezárjuk, ami mint látni fogjuk azért ez is jelent némi feladatot.
4.3.1.
Forráskód
Az ablakok tesztelésére szolgáló programok forráskódjai (6.16. Kódrészlet) mindössze két sorban térnek el egymástól, ami a gyakorlatban nem jelent érdemi különbséget, ugyanakkor rámutat arra, hogy a Dogtail használatával két API is rendelkezésünkre áll az applikációk teszteléséhez. Ezek a tree, illetve a procedural API. El˝obbi egy objektumorientált megközelítést alkalmazva teszi lehet˝ové, hogy a felhasználó felület egyes elemeit – gombok, ablakok, menük – elérjük, azokon m˝uveleteket végezzünk, illetve a köztük fennálló összefüggéseket feltárjuk. Utóbbi az ablakokra, illetve azok elemeire – melyeket nevükkel hivatkozhatunk – ad fókuszt, illetve végez egyéb m˝uveleteket, aminek révén egyszer˝uen vezérelhetjük a tesztelend˝o alkalmazást. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
fromdogtailimportprocedural importunittest
fromdogtailimporttree
classGtkDemoTest(unittest.TestCase): defsetUp(self): fromdogtailimportutils self.pid = utils.run(’gtk3-demo’) self.app = procedural.focus.application(’gtk3-demo’)
importunittest classGtkDemoTest(unittest.TestCase): defsetUp(self): fromdogtailimportutils self.pid = utils.run(’gtk3-demo’) self.app = tree.root.application(’gtk3-demo’)
deftearDown(self): import os, signal, time os.kill(self.pid, signal.SIGTERM) time.sleep(0.5)
deftearDown(self): import os, signal, time os.kill(self.pid, signal.SIGTERM) time.sleep(0.5)
deftestGtkDemo(self): pass
deftestGtkDemo(self): pass if __name__ == ’__main__’: unittest.main()
if __name__ == ’__main__’: unittest.main()
Kódrészlet 4.2. Minimálisan szükséges teszt tree, illetve procedural API használata mellett 4a
minimalizálása és a maximalizálás az ablakkezel˝o hatáskörébe tartoznak
19
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
A következ˝o kód – a fejlesztési példától némiképpen eltér˝oen – nem szorítkozik a minimálisan szükséges kódsorok ismertetésére, ennél egy kicsit tovább megy. Ennek oka kett˝os. Egyrészr˝ol minimálisan alig néhány sorra van szükség, másrészr˝ol igyekszünk egy, a valós életben is használható kóddal szolgálni, aminek része egy tesztelési kertrendszer (framework), ebben az esetben a Python unittest modulja. Nem mellesleg a Dogtail saját tesztjei is ezt a modult alkalmazzák, a következ˝okben ismertetettekhez nagyon hasonló, esetenként teljesen azonos módon. Ahogy a korábbiakban, úgy itt sem ismertetjük a nyelvi eszközökb˝ol adódó sajátosságokat, hacsak annak nincs kifejezett hatása a tesztelésre, így a unittest modul is csak olyan mértékben kerül ismertetésre, amennyire az a megértés szempontjából szükséges. Tesztelend˝o alkalmazásnak a GTK demó programját választottuk, ami csaknem minden widgetre ad példát és része azon fejleszt˝oi korábban már említett csomagnak, mely a C nyelv˝u fordításhoz szükséges. 1. sor A két különböz˝oséget adó sor egyike, ahol az Dogtail el˝obbiekben említett procedural, illetve tree moduljait importáljuk. Az egyes példákban az importált modulnak megfelel˝o módszereket, illetve eszközöket alkalmazzuk. 1. sor A tesztelésre szolgáló osztályunk a Python beépített unittest nev˝u moduljának TestCase osztályából származik, vagyis ezen modult használjuk a tesztek implementálására. Maga a modul természetesen nem feltétlenül szükséges a Dogtail alapú teszteléshez, ugyanakkor számos olyan eszközt nyújt melynek a kés˝obbiekben még hasznát látjuk. 2. sor A unittest modul TestCase osztályából származó osztályok setUp, illetve tearDown (11. sor) nev˝u függvényei a tesztfuttatása során automatikusan meghívódnak minden egyes teszteset el˝ott, illetve után, lehet˝oséget adva a összes tesztesetre nézve közös el˝okészít˝o, illetve utómunkák elvégzésére. Ebben az esetben az el˝okészíts nem áll másból, mint hogy a utils modul megfelel˝o függvényének segítségével elindítjuk a GTK+ demó alkalmazását, amit az egyes tesztelési feladatok bemutatására használunk majd fel. 4. sor Itt történik a GTK+ demó alkalmazásának tényleges futtatása, ahol a visszatérési értékként kapott folyamatazonosítót (PID) eltesszük kés˝obbi használatra (13. sor). 11. sor Ahogy arról szó esett a setUp függvényhez hasonlóan egy speciális függvény, amit Python unittest modulja automatikusan hív meg, minden egyes teszteset lefutását követ˝oen. 13. sor A korábban elmentett (4. sor) folyamatazonosítót felhasználva küldünk kilépésre (termination) felszólító szignált a GTK+ demó programjának, mivel annak f˝oablaka nem tartalmaz olyan elemet (pl: menüpont, gomb, . . . ), melynek segítségével a kilépést el lehetne érni. 14. sor Levezetésként 5 másodperc várakozás következik minden tesztesetet követ˝oen elkerülend˝o az AT-SPI túlterhelését. 16. sor Ez a függvény voltaképpen csak demonstrációs jelleggel kapott helyet ebben a példaprogramban, bemutatandó, hogy a nevükben test el˝otaggal rendelkez˝o függvényeket a Python unittest modulja tesztnek tekinti és ennek megfelel˝oen futtatja o˝ ket. 20. sor A unittest modul main függvénye példányosítja a TestCase osztály leszármazottjait – azaz a teszteset implementációkat tartalmazó objektumokat hoz létre –, majd futtatja az azokban lév˝o – az el˝obbiekben említett nevezéktan szerinti – tesztfüggvényeket. Bizonyos körülmények megfelel˝oen – például, hogy a függvények futása során keletkezett-e kivétel – hibásnak, vagy sikeresnek tekinti e teszteket. A unittest modul számos funkció révén könnyíti meg a tesztel˝oi munkát, melynek részleteir˝ol a modul dokumentációjában olvashatunk, illetve a további részekben lesz szó.
4.3.2.
Futtatás
Az imént ismertetett tesztfuttatása rendkívül egyszer˝u eredményre vezet, lévén mindösszesen egy tesztesetet (GtkDemoTest) és azon belül is csak egyetlen tesztet (testGtkDemo) tartalmaz, mely reményeink szerint sikeresen fut majd le. A szkript futtatása még ebben az egyszer˝u esetben is többféleképp lehetséges, már ami a megadható paramétereket illeti. A paraméterek nélkül a szkript az összes tesztesetének összes tesztfüggvényét futtatja, ugyanakkor lehet˝oség van paraméterként egy teszteset, vagy akár egy azon belüli tesztfüggvény megadására is. El˝obbi révén a megadott teszteset összes tesztfüggvénye futtatható, míg az utóbbi eset egy konkrét tesztfüggvény futtatására használható. python dogtail_minimal_tree.py python dogtail_minimal_tree.py GtkDemoTest python dogtail_minimal_tree.py GtkDemoTest.testGtkDemo
A tesztesetek lefutásának mikéntjér˝ol maga a teszt szolgáltat információt futtatáskor megjelenítve az összes futtatott teszteset számát, a futtatás idejét, a sikeresen, a sikertelenül, illetve a hibásan lefutott teszteket. Utóbbi két esetben a hiba okával, illetve a hozzájuk tartozó híváslistával (backtrace) együtt, ami alapjául szolgálhat a hibakeresésnek.
20
5. fejezet
Szignálkezelés dióhéjban Ebben a részben a szignálok kezelésének elméleti kérdéseir˝ol, illetve gyakorlatáról esik szó, amihez egy új példaprogramot veszünk górcs˝o alá, ami forráskódját tekintve némiképp ugyan bonyolultabb a korábbiakban tárgyaltaktól, m˝uködésére nézve azonban nem sokban különbözik attól.
5.1.
Fogalmak
El˝oször is a korábban már megismert alapfogalmakat vesszük újra el˝o, most azonban általános ismertetésük helyett a témánkhoz konkrétan kapcsolódó specifikumaikat vázoljuk fel. Main Loop. Ahogy arról az el˝oz˝o részekben már szó esett – más felületprogramozási nyelvekhez teljesen hasonlóan – a GTK is eseményvezérelt (event-driven) módon m˝uködik. Ez annyit tesz, hogy felhasználói interakciók bekövetkeztéig – figyelmen kívül hagyva az ütemezett eseményeket és néhány, a kés˝obbi részekben részletezend˝o funkciót –, a GTK a saját f˝ociklusában (main loop) várakozik, lényegében tehát a szoftver futását maguk a bekövetkez˝o események vezérlik, hiszen ezek híján a várakozás sosem ér véget. Felhasználói interakció lehet például az egér megmozdítása, vagy egy kattintás, esetleg egy billenty˝u lenyomása, vagy éppen felengedése. Ahhoz, hog az alkalmazás az eseményvezérelt szakasza megkezd˝odhessen be kell lépni a GTK f˝ociklusába. Erre, az el˝oz˝o részben taglalt minimalista alkalmazások forráskódjából már ismer˝os gtk_main(), gtkmm esetén a Gtk::Main::run(), illetve a Python változat esetén a Gtk.main() függvény szolgál. Ha az imént említett események közül bármelyik bekövetkezik, az addig „alvó” f˝ociklus úgymond „felébred”, azonosítja az eseményhez tartozó felületi elemet, majd a bekövetkezett eseményt továbbítja (propagate) az azonosított widget, vagy widgetek felé. A vezérlés ezt követ˝oen a f˝ociklusba tér vissza, ahol folytatódik a várakozás a következ˝o eseményre. Signal. A szignál voltaképpen egy névvel azonosított, a bekövetkezett esemény továbbítására szolgáló üzenet, amit az osztály definiál, hogy példányai értesíthessék az események bekövetkeztér˝ol az az iránt érdekl˝od˝oket. Ilyen üzenetb˝ol számos létezik, hisz az egyes widgettípusokon különböz˝o események lehetnek értelmezettek. Gomb esetén a rá történ˝o kattintás, egy legördül˝o menünél az egér menüelem fölé történ˝o mozgatása, míg egy widgetnél például annak átméretezése. Minden ilyen esemény rendelkezik saját névvel, melynek révén hivatkozni tudunk rá (pl: button-pressed, enter-notify-event, size-request). A szignálok örökl˝odnek, azaz egy specifikus widget, mint amilyen mondjuk egy RadioButton, vagy egy CheckButton, minden olyan szignállal rendelkezik, amivel o˝ se a Button, vagy akár annak az o˝ se az a Widget típus rendelkezett. A szignálok egyrészr˝ol arra szolgálnak, hogy a GTK rendszerén belül az egyes widgetek egymással kommunikálhassanak. Ha például egy gombot lenyomunk, akkor azt (illetve annak részeit) újra kell rajzolni, ha egy menüelemet kiválasztunk, azt át kell színezni, illetve az esetleges almenüpontokat ki kell rajzolni, míg átméretezésnél az egyes widgetek helyigényét újra ki kell számolni. Másfel˝ol ha a program írói valamely esemény bekövetkezésér˝ol értesülni szeretnének, megadhatnánk eseménykezel˝o függvényeket, melyek ezen esetekben meghívódnak. Callback. Ezen eseménykezel˝o függvények elnevezése a GTK terminológiában callback. Az egyes eseményekhez tartozó kezel˝ofüggvények prototípusai a szignál fajtájától függenek. A C nyelv˝u változat esetén els˝o paraméterük jellemz˝oen az a Widget – pontosabban szólva Object, hiszen a szignálkezelés ezen a szinten került implementálásra a Glib-ben – melyen az esemény kiváltódott. Ezt a paramétert követik a szignálhoz kapcsolódó egyéb jellemz˝ok, az utolsó pedig a szignál bekötésekor megadott, úgynevezett user data, amir˝ol a példaprogram kapcsán részletesebben szólunk. Elöljáróban csak annyit, hogy ez egy meglehet˝osen kényelmetlen és gyakorta nehézkesen használható megoldás, melyre a C++, illetve Python nyelv˝u változatok kínálnak kényelmes alternatívát.
21
5.2.
Szignálkezelés
Az el˝oz˝o szám módszertanától eltérve az alábbiak szerint elemezzük a kódokat: • külön-külön vesszük számba ez egyes nyelvi változatok sajátosságait • el˝oször a C, illetve a C++ nyelv˝u verziónak fogunk neki, ezt követ˝oen bemutatjuk mennyiben más a helyzet, Python nyelv˝u változatokban • a kódot nem sorfolytonosan, hanem a futás logikája szerint követjük, lévén egy kicsit is bonyolultabb esetben – mint amilyennek az alábbi példa is mondható – már ez a logikusabb
5.2.1. C, illetve C++ nyelvu˝ változat 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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
#include
#include #include
staticvoidon_button_clicked( GtkWidget *widget, gpointerdata) { g_print ("%s\n", (constchar *) data); }
voidon_button_clicked(constGlib::ustring &hello_msg) { std::cout<< hello_msg<< std::endl; } classMyWindow : publicGtk::Window { protected: virtualboolon_delete_event(GdkEventAny *event)
staticgbooleanon_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data ) { g_print ("deleteeventoccurred\n"); returnTRUE; }
{ std::cout<< "deleteeventoccurred" << event<< std::endl; returntrue; } public: MyWindow() : button("HelloWindow!") {
GtkWidget * my_window_new() { GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL); GtkWidget *button = gtk_button_new_with_label ("HelloWindow!"); g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (on_delete_event), NULL); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (on_button_clicked), "HelloWindow!"); g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (gtk_widget_destroy), G_OBJECT (window));
button.signal_clicked().connect( sigc::bind(sigc::ptr_fun(on_button_clicked), "HelloWindow!")); button.signal_clicked().connect( sigc::mem_fun(*this, &MyWindow::hide));
gtk_container_add (GTK_CONTAINER (window), button);
add(button);
gtk_widget_show (button);
button.show();
returnwindow; }
} private: Gtk::Buttonbutton; };
int main(int argc, char *argv[]) { GtkWidget *window;
int main(int argc, char *argv[]) {
gtk_init (&argc, &argv);
Gtk::Main kit(argc, argv);
window = my_window_new(); gtk_widget_show (window);
MyWindowwindow;
gtk_main();
Gtk::Main::run(window);
return 0;
return 0;
}
}
Kódrészlet 5.1. Szignálok kezelése C, illetve C++ nyelven
Általánosságok 1 - 2 sor Az fejlécállományok beszerkesztésének sajátosságairól már esett szó, így az egyedüli specifikum itt a C++ változat által beszerkesztett iostream fejlécfájl, amire csupán a képerny˝ore történ˝o íráshoz lesz szükség. A GTK+ esetén – mivel a gtk.h minden szükséges fejléc állományt maga felsorol – használni tudjuk a Glib erre a célra használatos függvénylét (g_print). 22
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
50 - 62 sor A main függvényben alkalmazottak gyakorlatilag teljesen azonosak a korábbi minimális példánál ismertetettekkel, így itt err˝ol leginkább csak annyit érdemes megjegyezni, hogy a C++ változat – élve a nyelv adta lehet˝oségekkel – egy saját osztály segítségével zárja egységbe egy gombbal kiegészített ablakunkat, míg a C változatban egy – a saját ablak létrehozására szolgáló – függvény segítségével igyekeztünk ezt módszert valamelyest követni. Természetesen a GTK+ esetén is van mód származtatásra, de nem oly kézenfekv˝o módon, mint amikor a C++ nyelvet használjuk, ennél fogva ennek ismertetése még várat magára. Szignálok a GTK+ nyelvu˝ kódban 24 - 25 sor A létrehozott ablak, illetve gomb tárolására szolgáló változók típusa GtkWidget, a specifikusabb GtkWindow, illetve GtkButton helyett. Ennek magyarázata, hogy minden olyan függvény a GTK+-ban, melynek segítségével egy új widgetet hozhatunk létre – gyakorlatilag a _new vég˝u metódusok – egy GtkWidget típusú objektumra mutatóval tér vissza. Ennek több oka is van. Egyrészr˝ol kényelmi, hogy elkerülhessük a folytonos típuskényszerítéseket, hisz számos esetben olyan függvényeket használunk, melyek amúgy is GtkWidgeteket kezelnek, tehát ilyen típusra mutatót vesznek át els˝o paraméterként. Másrészr˝ol ha egy specifikus – mondjuk GtkButton-t kezel˝o – függvényt akarunk hívni, akkor vagy fordítási – vagy ami inkább javasolt – futás idej˝u típuskényszerítés alkalmazandó (pl: GTK_BUTTON, GTK_WINDOW), aminek megvan az a komoly el˝onye, hogy ha sikerült a mutatónkat, vagy a mutatott objektumot valamilyen módon korrumpálni, akkor arra viszonylag hamar fény tud derülni. Szignálkezel˝o függvények felkötése. 27. sor Az els˝o szignálbekötés. Viszonylagos egyszer˝usége ellenére számos apróságra érdemes figyelmet fordítani. Az els˝o maga a g_signal_connect kulcsszó, ami függvénynek t˝unhet, pedig ugyanúgy, mint a g_signal_connect_swapped, makró, amik a g_sinal_connect_data függvényt burkolják. A soron következ˝o érdekesség a G_OBJECT makró, ami futás idej˝u típusellen˝orzést hajt végre a neki megadott paraméteren, majd egy GObject típusra mutatóval tér vissza. A megrögzött C++ programozók joggal kérdezhetik, mi szükség erre, hisz egyfel˝ol majd elvégzi a típusellen˝orzést a fordító, meg hát a GtkWindow típus úgy is leszármazottja a GObject ”osztálynak„. Ez így is lenne, na de ez itt C, tehát o˝ s-, illetve származtatott osztályokról csak logikai értelemben lehet szó, a típusellen˝orzés tehát nem végezhet˝o, s˝ot minden esetben a hívott függvénynek megfelel˝o típuskényszerít˝o makrót célszer˝u alkalmazni. A második paraméter a szignál neve, amivel azt adjuk meg, hogy az el˝oz˝o paraméterként megadott object melyik szignáljára is szeretnénk kezel˝o függvényt (callback) kötni. A harmadik paraméter azon függvény címe, aminek meghívódását ki szeretnénk váltani az esemény bekövetkezésekor. A függvénynevet itt is egy makró segítségével adjuk át, ami az el˝oz˝oekhez hasonlóan C nyelvi hiányosságokra vezethet˝o vissza. Mivel a meghívandó callbackek prototípusai igen sokfélék lehetnek (ami magából a példából is látszik valamelyest és ezek mind külön típusnak min˝osülnek a C nyelvben ezért ahányféle callback variáció létezik, annyiféle g_signal_connect függvényre lenne szükség. Könnyen belátható, hogy a jogos lustaság más irányba vitte a GTK+ fejleszt˝oit. A G_CALLBACK tulajdonképpen egy fordítási idej˝u típuskényszerítés egy általános függvénytípusra, amivel ugyan megoldottuk, hogy csak egyetlen g_signal_connect_data függvényre legyen szükség, de elvesztettünk minden nem˝u típusbiztosságot. Ha például egy az adott szignálnak nem megfelel˝o típusú függvényt adunk meg paraméterként, amit a példabeli függvénynevek felcserélésével könnyen megtehetünk, csúnya meglepetésekben lesz részünk, de csak futásid˝oben. Nem hagyhatjuk továbbá figyelmen kívül a C nyelv azon sajátosságát sem, hogy az átadott függvényparaméterek átvétele nemkötelez˝o, azaz ha kevesebb paraméterrel definiálunk egy függvény, mit amennyivel hívni szeretnénk voltaképpen nem követünk el b˝unt, ez viszont tovább bonyolítja a helyzetet. Az utolsó paraméter az úgynevezett user data, ami arra szolgál, hogy az eseménykezel˝o függvényünknek olyan adatokat adjunk át, amik az esemény megtörténtéb˝ol nem következnek. Ilyenek lehetnek például más widgetek címei, ahogy azt látni is fogjuk. Ez esetben az átadott paraméter NULL, ami szintén egy makró ami egy jól nevelt ((void*) 0) kifejezésre fejt˝odik ki C kód esetén. Zárszóként ehhez a sorhoz csak annyit, hogy a delete_event eseményt az ablakkezel˝o váltja ki, akkor, amikor az ablakot valamilyen módon (billenty˝uzet, menü, egér) bezárjuk. A delete-event szignál blokkolása. 12. sor Ez a delete_event szignálkezel˝o függvénye, aminek – néhány más szignálkezel˝o függvényhez hasonlóan – egy gboolean értékkel kell visszatérnie, ami azt határozza meg, hogy az általunk a szignálkezel˝oben végrehajtottak után a GTK lefuttassa-e saját szignálkezel˝o rutinját, vagy sem. Jelentése voltaképpen tehát az, hogy a saját magunk a szignált mindenre kiterjed˝oen kezeltük-e. Ennek megfelel˝oen ha a visszatérési érték 0 – azaz logikai hamis – , akkor végrehajtódik a GTK+ adott szignálhoz kapcsolódó alapértelmezett kezel˝o függvénye, ellenkez˝o esetben értelemszer˝uen arra utasítjuk a GTK-t, hogy a szignál további feldolgozásától tekintsen el. Itt érdemes felhívni a figyelmet arra, hogy mivel a C nyelvben – a C++-al ellentétben –, nincs bool típus annak analógiájára definiálták a gboolean típust (ami tulajdonképpen egy int) és a két megfelel˝o logikai értéket makróként (TRUE, FALSE). Ebben a konkrét esetben (delete_event szignál) az alapértelmezett szignálkezel˝o a gtk_widget_destroy függvény, vagyis ha nem kötünk fel saját szignálkezel˝o függvényt, vagy logikai hamis értékkel térünk vissza a kezel˝o 23
függvényb˝ol, akkor a window objektum megsemmisül, az ablak bezárul. Logikai igaz érték visszaadásával elérhet˝o, hogy hiába próbáljuk akár a jobb fels˝o sarok x gombjának megnyomásával, akár valamilyen billenty˝ukombináció révén bezárni az ablakot ez a próbálkozás sikertelen lesz, ellenben minden ilyen próbálkozás egy újabb sor kiírást eredményezi. Az ablak bezárása. 29. sor Az el˝oz˝ohöz teljesen hasonló módon itt a destroy szignálra kötünk be eseménykezel˝ot, ami a widget életciklusának végén váltódik ki. Egy konténerben lév˝o widget esetén ez a konténerb˝ol való eltávolítás következménye – már ha valaki nem tart külön referenciát az eltávolított widgetre –, legfels˝o szint˝u widgetek (toplvel) esetén – mint amilyenek az ablakok is – ez jellemz˝oen a gtk_widget_destroy függvény meghívásának folyománya, lévén az ilyen widgetek automatikusan nem semmisülnek meg, err˝ol nekünk explicit módon kell gondoskodnunk (35. sor). A destroy szignál kezelése általánosságban nézve ritka, jelen esetben is csak az a szerepe, hogy a program valamilyen módon ki tudjon lépni. Az ablak bezárása alapértelmezetten ugyan ezt eredményezné, de a delete-event szignálra kötött kezel˝ofüggvényben nem hogy ezt nem tesszük meg, de még az ablak bezáródásást is meggátoljuk. Mikor a destroy szignálra kötött kezel˝ofüggvény meghívódik, ablakunk épp megsz˝un˝ofélben van, ugyanakkor ha ez az eset áll is fenn a programunk futása annak ellenére sem érne véget, hogy az ablakunk bezáródik, hiszen a main loopból nem lépnénk ki. Ezen helyzet elkerülésére eseménykezel˝oként a main loopból való kilépésre szolgáló gtk_main_quit függvényt kötjük fel. Érdemes megjegyezni, hogy bár a gtk_main_quit függvény definíciója (void (*) ()) nem felel meg tökéletesen a destroy szignál által elvártaknak (void (*) (GtkWidget *, gpointer)) ez voltaképpen nem jelent problémát, hiszen a típusok különböz˝oségét a G_CALLBACK makró által alkalmazott típuskényszerítés elrejti a fordító el˝ol, futásid˝oben pedig a gtk_main_quit egész egyszer˝uen nem veszi át a szignálkezel˝ot meghívó kódtól a két függvényparamétert. 35. sor A 32. sortól csak a meghívandó eseménykezel˝o függvényben, illetve az annak átadandó paraméterekben sorrendjében tér el. Ahogy az a függvény nevéb˝ol (g_signal_connect_swapped) következik, arról van szó, hogy a gomb lenyomásakor meghívandó callback – jelen esetben a gtk_widget_destroy – paramétereiben a user_data, illetve az az object, amin az esemény kiváltódik, felcserélésre kerül. Kicsit konkrétabban fogalmazva a user_data lesz a callback els˝o paramétere és a gomb a második. Mivel itt a callback a gtk_widget_destroy függvény, ami paraméterként mondjuk úgy, a törlend˝o widgetet várja, a user_data pedig az ablakunk, nem nehéz kitalálni, hogy a gombra való kattintás eredményeként az ablak meg fog sz˝unni, de csak azután, hogy a „Helló Window!” üzenet megjelent a konzolban. Gomb lenyomásának kezelése. 32. sor Eseménykezel˝o függvény bekötése a gomb clicked szignáljára, ami a gomb lenyomásakor hívódik meg, aminek egyetlen különlegessége, hogy itt a szignálkezel˝o definíciója pontosan megfelel a szignál által elvártaknak, ugyanakkor a G_CALLBACK makró mégis szükséges, mivel a g_signal_connect azt a típust várja, amire az on_button_clicked függvényt a makró kényszeríti. 4. sor A fenti állítás – miszerint az ablak csak a kiírást követ˝oen sz˝unik meg – csak azért igaz, mert a on_button_clicked függvény, mint eseménykezel˝o el˝obb kerül felkötésre, mint a gtk_widget_destroy, valamint azért, mert az eseménykezel˝ok alapvet˝oen a felkötés sorrendjében kerülnek meghívásra. Fordított esetben el˝obb hívódna meg a destroy az ablakra, ami – sok egyéb mellett – leköti az eseménykezel˝o függvényeket, így a kiírást nem is látnánk. Egyebek. 39. sor A nyomógomb hozzáadása az ablakhoz. 41 - 57 sor A létrehozott widgetek megjelenítése. 43. sor Belépés az eseményvezérelt szakaszba. Fentiek ismeretében nagy biztonsággal jósolhatjuk meg példaprogramunk m˝uködését. Az elindított alkalmazás egy ablakot jelenít meg, melyben egy Helló Window! feliratú gomb lesz. Az ablak bezárásával hiába próbálkozunk egér, vagy billenty˝uzet segítségével, ezen kísérletek eredmény csupán egy-egy ”delete event occurred„ sor a konzolban. Ha azonban le találnák nyomni gombunkat az ablak hirtelen elt˝unik a konzolban egy ”Helló Window!„ felirat jelenik meg és a program kilép. Lássuk, hogy érhetünk ehhez teljesen hasonló funkcionalitást C++-ban.
24
Szignálok a gtkmm nyelvu˝ kódban Szignálkezel˝o függvények felkötése. 50 - 62 sor Ahogy azt az általánosságokat taglaló részben említettük Gtk::Window helyett MyWindow típust használunk f˝oablakunk létrehozásához. Mivel azonban a MyWindow publikusan származik a Gtk::Window típusból ez a gtkmm számára nem jelent különbséget. A C változathoz képest a származtatás itt nem csupán ”logikai”, vagyis minden a C++-an megszokott el˝ony könnyedén realizálható. Erre példa, hogy a származtatás miatt nincs szükség semmilyen típuskényszerítésre mikor a Gtk::Main::run függvényt hívjuk, ami pedig egy Gtk::Window referenciát vesz át paraméterként. 21 - 44 sor Saját osztályunk konstruktorában megtehetjük mindazokat a lépéseket, melyeket a C nyelv˝u változat esetén a my_window_new függvényben implementáltunk. Úgy is mint a szignálok felkötése, a gomb hozzáadása az ablakhoz, a widgetek megjelenítése. Az egységbezárás ezen el˝onyén túl a származtatásból fakadó örömöket is élvezhetjük, ugyanakkor persze az ebb˝ol fakadó kötelességeknek is eleget kell tenni. Ez esetben ez a konstruktor meghívását jelenti, ami rejtett módon megy végbe. Az o˝ sosztály konstruktorának explicit hívásának hiányában a Gtk::Window azon konstruktora fut le, ami paraméterek nélkül is hívható. Másrészr˝ol viszont az adattagként tárolt GtkButtont (button) is inicializálnunk kell. Itt is lehetne közvetve, implicit módon hívni a paraméter nélküli konstruktort, azonban kézenfekv˝obb azt a változatot használni, amivel egyszerre a gomb feliratát (label) is megadhatjuk, így egy hívás a kés˝obbiekben megspórolható. Külön szót érdemelnek a szignálok bekötései. Különösebb programozó géniusz nem kell, hogy felfedezzük a szignálok eléréséhez egy signal_szignálnév szerkezet˝u hívását használjuk fel. Az ilyen hívások egy, a Glib::SignalProxyBase osztályból származó objektumot adnak vissza, amik connect nev˝u metódusai valósítják meg azt, amit a GTK+ esetén a g_signal_connect makró tett meg, vagyis egy adott widget, adott szignáljára eseménykezel˝o felkötését. El˝onye ennek a módszernek, hogy típusbiztos, azaz a connect paraméterként csak olyan függvényt (slot) fogad el, melynek típusa megfelel az adott szignálnál leírtakkal. További el˝ony, hogy a slotokhoz nem csupán egy user data csatolható, hanem tetszés szerinti számú, s ezek típusa is ellen˝orzésre kerül fordításkor. Amennyiben azonban sikerül csupán egy apróságot is elírnunk a szignál bekötésénél, vagy a slot típusának megadásánál – a sablonokkal (template) történ˝o megvalósításnak hála –, akkor jellemz˝oen több oldalas, nehezen kibogarászható hibaüzenettel találhatjuk szemben magunkat. 32 - 35 sor Lássuk akkor miként is érhet˝o el ugyanaz gtkmm esetén, mint ami korábban GTK+ használatával. Els˝o pillantásra is szembeszök˝o, hogy mindkét sorban találunk olyan hívást, amik nem a Gtk névtérben definiáltak. Ennek az az oka, hogy a gtkmm a szignálkezelést egy küls˝o – libsigc++ nev˝u – függvénykönyvtárral valósítja meg. A két eseménykezel˝o felkötése közötti különbséget az eseménykezel˝o függvények típusa adja lássuk ezt részletesebben. 32. sor Ha a megadni kívánt függvény nem köt˝odik objektumhoz – legyen ez egy osztály statikus tagfüggvénye, vagy akár egy tisztán C nyelv˝u kódból származó függvény – slot létrehozásához a sigc::ptr_fun alkalmazandó. Ebben a konkrét esetben a slot létrehozásán túl, paramétereket is hozzákapcsolunk a clicked esemény bekövetkeztekor meghívandó függvényhez. Ennek eszköze a sigc::bind, melynek els˝o paramétere egy slot, a továbbiak pedig a csatolandó paraméterek. Itt csupán egy ilyen van, a gomb lenyomásának hatására kiírandó üzenet szövege. Ez persze kissé kényszeredett, hiszen a paraméter értéke soha nem változik, így ennek igazi hasznát ezen a példa alapján még nehéz belátni. 4. sor Eseménykezel˝o függvényünk a lehet˝o legegyszer˝ubb, csupán azt szemlélteti miként is kell az átadott paramétereket használni. Ez esetben annak értékét a standard kimenetre kiírni. M˝uködését és funkcióját tekintve a C-s változat azonos nev˝u függvényével analóg. 35. sor Ha a megadni kívánt eseménykezel˝o egy osztály tagfüggvénye, akkor a sigc::mem_fun használható arra, hogy slotot hozzunk létre az osztály egy példányából, illetve az osztály tagfüggvényéb˝ol, ebben a sorrendben átadva o˝ ket a függvénynek, utóbbit a teljes névtérlistával együtt. Természetesen az imént említett sigc::bind, az el˝obbiekhez hasonlóan módon itt is alkalmazható. Ez a hívás épp ugyanazt a célt szolgálja, mint a GTK+ változat azonos sorszámú sora, azaz hogy az alkalmazásunkból annak ellenére is ki lehessen lépni, hogy az ablak bezáró gombjának hatására a itt sem történik semmi egyéb, mint kiíródik a ”delete event occurred„ szöveg a standard kimenetre. Míg legutóbb a gtk_widget_destroy függvényt kötöttük fel eseménykezel˝oként, itt a Gtk::Widget osztály hide függvényét használjuk, aminek hatására a GTK f˝ociklusa kilép, mivel annak futtatásakor (59. sor) megadtuk ablakunkat paraméterként. Átgondolva a m˝uködést jogosnak tekinthet˝o, hiszen látható f˝oablak hiányában a további m˝uködésnek nem sok értelme van, ugyanakkor a C változat gtk_widget_destroy függvénye helyett a C++ változatban a delete hívással lehetne úgymond jelezni, hogy az ablakunkra nincs tovább szükség, viszont ez nem célszer˝u, hiszen az a main függvényben egy lokális változó.
25
A delete-event szignál blokkolása. 12. sor A GTK+ változathoz képesti komoly különbség, hogy itt a delete-event szignál blokkolása nem egy felkötött eseménykezel˝on keresztül valósul meg – ezért is nincs a kódban olyan sor, ami erre a szignálra vonatkozna –, hanem az alapértelmezett eseménykezel˝o kerül felülírásra. A m˝uködés megértésének kulcsa a virtual kulcsszóban rejlik. Minden szignálhoz tartozik ugyanis egy – az adott widget által implementált – alapértelmezett eseménykezel˝o függvény, ami alkalmasint felülbírálható (ovverride). Ha ezt megtesszük, azzal a szignál kezelésének teljes folyamatát mi irányítjuk, ami mellett komoly érvek szólhatnak, de nem árt körültekint˝onek lenni. Ám ebben az esetben a cél pont annak a demonstrálása, hogy a gtkmm szabad kezet ad abban, hogy egy származtatott widget miként kívánja kezelni az o˝ sosztály eseményeit. A visszatérési érték szerepe ugyanaz, így a m˝uködés is azonos az el˝oz˝o – GTK+ nyelv˝u – példáéval. A szignálkezelésr˝ol összegzésképpen annyit, hogy alapvet˝oen két lehet˝oség kínálkozik arra, hogy az egyes widgetek eseményei kezeljük: • Callbackeket kapcsolni azon widgetek azon eseményihez, melyek számunkra érdekesek és ezekben megtenni a megfelel˝o lépéseket • Felülbírálni a widget saját eseménykezel˝ojét az örökl˝odés mechanizmusai útján. Erre mindkét változat esetén van lehet˝oség, ám a GTK+ megoldása kissé körülményes és nehezebben megérthet˝o, így annak ismertetése valmely kés˝obbi részre marad. A C++ nyelvi eszközeit kihasználva a gtkmm viszont ezt oly könnyedén oldja meg, hogy kár lett volna kihagyni a bemutatást annak ellenére is, hogy a módszerre ritkán van szükség, hiszen többnyire arról van szó, hogy a különböz˝o widgetpéldányok azonos szignáljainak kiváltódásakor más-más irányba szeretnénk terelni a program futását. A felülbírálás révén viszont arra nyílik lehet˝oség, hogy a szignál kezelésének módját változtassuk meg. Ha nem kívánunk egyebet tenni, mint ami amúgy is történne, hívjuk meg a felülbírált függvény szül˝oosztálybeli változatát. Ha azonban ez el˝ott, vagy után még valami másra is szükségünk van, megtehetjük, hogy csak a függvény közepér˝ol hívjuk a szül˝o metódusát, vagy akár el is hagyhatjuk az ha tudjuk mit és f˝oként hogyan szeretnénk kezelni.
5.2.2. Python nyelvu˝ változatok 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
from gi.repositoryimportGtk
from gi.repositoryimportGtk
defon_button_clicked(button, hello_msg): print(hello_msg)
defon_button_clicked(button, hello_msg): print(hello_msg)
defon_delete_event(my_window, event): print("deleteeventoccurred") returnTrue
classMyWindow(Gtk.Window): defdo_delete_event(self, event): print("deleteeventoccurred") returnTrue
defmy_window_new(): my_window = Gtk.Window() my_window.connect("destroy", Gtk.main_quit) my_window.connect("delete-event", on_delete_event)
def__init__(self): Gtk.Window.__init__(self) self.connect("destroy", Gtk.main_quit)
button = Gtk.Button("HelloWindow") button.connect("clicked", on_button_clicked, "HelloWindow") button.connect_object("clicked", Gtk.Widget.destroy, my_window) my_window.add(button)
self.button = Gtk.Button("HelloWindow") self.button.connect("clicked", on_button_clicked, "HelloWindow") self.button.connect_object("clicked", Gtk.Widget.destroy, self) self.add(self.button)
returnmy_window if __name__ == "__main__":
if __name__ == "__main__":
my_window = my_window_new() my_window.show_all()
my_window = MyWindow() my_window.show_all()
Gtk.main()
Gtk.main()
Kódrészlet 5.2. Szignálok kezelése Python nyelven Elöljáróban annyit érdemes megjegyezni a Python változat kapcsán, hogy az itt alkalmazott megoldások a C, illetve C++ változatból már ismertek, logikájuk hol a GTK+, hol pedig a gtkmm megoldásaira emlékeztetnek – függ˝oen természetesen attól is, hogy kihasználjuk-e a Python nyelv adta objektum-orientáltság lehet˝oségeit –, ötvözve azok el˝onyeit. Kihasználva a korábbiakban leírtakat, itt már csak a kifejezett nyelv specifikus részeket ismertetjük, a csupán szintaktikai elemekben eltér˝o részletekre nem térünk ki. Általánosságok. 1. sor A Gtk szimbólumok importálása, csakúgy, mint a korábbi, minimális példában.
26
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
24 - 29 sor Ez a rész gyakorlatilag azonos a korábbi, minimális példánál ismertetettekkel, azzal a különbséggel, hogy a delete-event szignál helyet az ablakunk destroy szignáljára kötjük rá a programból való kilépéshez vezet˝o main_quit függvényt, melynek magyarázat épp az, mint a korábban C változatnál, err˝ol azonban még esik szó. A delete-event szignál blokkolása. 14. sor A korábbiakhoz hasonlóan a delete-event szignál elnyomására – függ˝oen attól, hogy objektum-orientált megközelítést alkalmazunk-e vagy sem – két lehet˝oség kínálkozik. Vagy egy szignálkezel˝o függvényt kötünk fel, amiben logikai igaz értékkel (True) térünk vissza, azt mondva ezzel a GTK szignált feldolgozó kódjának, hogy ezt az eseményt kezeltük, vagy saját osztályunkban írjuk felül az alapértelmezett eseménykezel˝ot (do_delete_event), amiben hasonlóképpen járunk el. Mivel a Python nyelvben gyakorlatilag minden függvény virtuális ezt minden további nélkül megtehetjük. 7. sor A kezel˝o függvény a nyelvi különbségekt˝ol eltekintve teljesen azonos a C, illetve C++ nyelv˝u változatokéval, vagyis mindkét függvény paraméterként megkapja a kezelt eseményt leíró adatstruktúrát, illetve azt az objektumot amin az esemény kiváltódott. Kicsit korrektebbül fogalmazva az objektum-orientált változat valójában azon objektumra kap referenciát, melynek osztályához a kezel˝o függvény tartozik, de ebben az esetben ez a kett˝o egybeesik. Gomb lenyomásának kezelése. 17. sor Mivel a Python esetén a widgetek – a C++ változathoz hasonlóan – valódi objektumok, az eseménykezel˝o függvények bekötése az objektumon keresztül történik. Az eseménykezel˝o függvények els˝o paramétere maga az objektum lesz. A szignál bekötésekor megadott további paraméterek a kezel˝o függvénynek adódnak át. A C változattal ellentétben – ahol csak egy paraméter adható meg – a Python képes egyszerre több paraméter átadására is – éppúgy, mint a gtkmm – ugyanakkor itt természetesen olyan szigorúan vett típusellen˝orzésr˝ol nem beszélhetünk, mint a C++ nyelv esetén. Az ablak bezárása. 18. sor Az ablak bezárásának logikája azonos a C változatnál elmondottakkal, vagyis az ablak destroy szignáljának kezel˝ojeként a main_quit függvényt adjuk meg, azaz az esemény bekövetkeztekor a Gtk f˝ociklusa, egyszersmind a programunk is kilép. 13. sor Ezt úgy érjük el, hogy gombunk clicked szignáljának kiváltására meghíjuk az ablak destroy függvényét. Hasonlóan a C változathoz, itt is alkalmazunk némi cselt. Mivel ott csak egy paraméter adható át – pontosabban kett˝o, hiszen az els˝o maga az objektum, aminek a szignáljára a kezel˝ofüggvényt felkötöttük –, a paramétereket meg kell fordítanunk, hogy az általunk megadott adat kerüljön az els˝o helyre, vagyis ez legyen a gtk_widget_destroy által megkapott egyetlen paraméter. Voltaképpen a connect_object ugyanezt a célt szolgálja, így ellentétben a connect függvénnyel ennek csak egy paramétere van, mely ez esetben maga az ablakunk, amit a destroy függvénynek adunk át.
5.2.3.
Fordítás és futtatás
A korábbiakhoz hasonlóan az alábbi parancssorok segítségével fordíthatóak elemzett programjaink: gcc gtk_signal.c -o gtk_signal ‘pkg-config --cflags --libs gtk+-3.0‘ g++ gtkmm_signal.cc -o gtkmm_signal ‘pkg-config --cflags --libs gtkmm-3.0‘
Próbálkozzunk ezúttal a ./gtk_signal, ./gtkmm_signal, illetve python gtk_signal.py parancsokkal abban a könyvtárban, ahol a fordítást elkövettük, illetve a Python forrást tartjuk.
5.2.4.
Eredmény
Bármily hihetetlen ezúttal sem történik sok egyéb, mint a korábbi minimális példa esetén. A különbség remélhet˝oleg annyi, hogy a meglepetéssel teli borzongást legutóbb ablakunk váratlan felbukkanása, míg most a bennünk szikraként felvillanó megértés okozza.
27
rész II
Alapvet˝o widgetek
28
6. fejezet
Ablakok 6.1.
Bevezetés
Ebben a részében a GTK-ban létrehozható különböz˝o ablaktípusok közös vonásait, valamint eltéréseit, illetve ezek okait vesszük sorra. Kitérünk egyrészr˝ol az egyes ablaktípusok létrehozásának sajátosságaira, azok widgetekkel való feltöltésére, másrészr˝ol a felhasználó interakciók kezelésére, ezzel együtt az ablakok bezárásának módjaira is, szem el˝ott tartva természetese a C, C++, illetve Python nyelv˝u változat azonosságait, különböz˝oségeit.
6.1.1. Popup és toplevel ablakok A popup ablakra, mint típusra ugyan ritkán lesz közvetlenül szükségünk, érdemes tudni, hogy a GTK ebben a tekintetben két fajta ablakot különböztet meg. A popup (felugró, felbukkanó) ablakokat, melyekre – valamilyen speciális célt szolgáló saját készítés˝u widgetekt˝ol eltekintve – csak néhány példa létezik (menu, tooltip), valamint a toplevel (legküls˝o, legfels˝o szint˝u) ablakokat, melyek csaknem minden GTK-s, illetve saját fejlesztés˝u ablak alapjául szolgálnak. Ha tehát az ablakra, illetve a hozzá kapcsolódó fogalmakra gondolunk, többségében egy toplevel ablakra gondolunk, és nem a popup1 típusúakra, melyekr˝ol talán nem is feltételeznénk els˝o ránézésre, hogy ablakok. Az ablakkezel˝o ezt az információt használja fel annak eldöntésére, hogy az adott ablakot milyen kerettel, dekorációval lássa el, illetve hogy általánosságban menedzselje e ablakot. Utóbbi – azaz a toplevel ablakok – esetben alapértelmezetten az ablakkezel˝o keretet, illetve a beállításoktól függ˝oen azon például bezáró, teljes mértre váltó, minimalizáló gombot jelenít meg. A popup típusú ablakokat az ablakkezel˝o nemcsak hogy nem dekorálja, de nem is menedzseli azokat, következésképp tehát számos – az ablakkezel˝o hatáskörébe tartozó – funkció, mint amilyen például a minimalizálás, vagy a maximalizálás nem is érhet˝o el. Bár kézenfekv˝o megoldásnak látszik a popup típus arra, ha egy dekoráció nélküli ablakot készítsünk, mégse ezt tegyük, az ilyen típusú megjelenésbeli sajátosságok beállítására léteznek külön függvények. Minden GtkWindow egyben konténer is, pontosabban fogalmazva egy GtkBin, azaz tartalmazhat egy további elemet gyerekként, ami természetesen szintén lehet egy konténer, így biztosítva, hogy számos elemet helyezhessünk el az elkészített ablakon belül.
6.1.2. Window és dialóg A window típus – azon belül is ahogy tárgyaltuk a toplevel window – közvetlen szül˝oje a dialog típusnak, számottev˝o különbség tulajdonképpen nincs is a kett˝o között. Egy dialog nem más, mint egy olyan window, melybe a GTK+ fejleszt˝oi néhány hasznos elemet helyeztek el. Konkrétabban fogalmazva minden dialógba egy függ˝oleges elrendezés˝u konténer widget (GtkBox), abba pedig egy, a gombok elhelyezésére szolgáló konténer (GtkButtonBox típusú action_area), valamint egy szeparátor (GtkSeparator) kerül, ebben a sorrendben mindkét esetben a konténer aljára helyezve2 . Ebb˝ol következik, hogy minden, amit egy dialógusba – annak elkészült után – tenni akarunk az a gombsor, valamint a vízszintes szeparátor fölött jelenik meg, függetlenül attól, hogy azt a pack_start(), vagy a pack_end() függvény segítségével helyezzük el a konténerben. A dialog típus tehát – a szeparátor által – függ˝olegesen ketté osztott window, ahol az alsó rész (action_area), ami általában a gombokat tartalmazza (pl.: Ok, Mégse, Súgó, . . . ), a fels˝o (content_area) pedig azokat az elemeket tartalmazza, amik a felhasználói számára a szükséges akcióhoz (pl.: adatbevitel, hibaüzenet megjelenítése, . . . ) szükséges.
6.1.3.
Modalitás
A több ablakkal történ˝o párhuzamos interakció tiltására szolgál a window modal tulajdonsága. Amennyiben egy ablak „modális” csak az abban az ablakban elhelyezked˝o widgetekbe történhet például bevitel, csak azokon váltódhat ki vala1 Más 2A
eszközkészletek a „popups” gy˝ujt˝ofogalom alá sorolják a dialógusokat, a GTK esetén azonban egy dialógus ablak mindig egy toplevel GtkBox típus pack_end() függvényét hívva.
29
milyen felhasználó által kezdeményezett esemény. Ezt kihasználva biztosíthatjuk például, hogy egy adatbeviteli ablak3 bezárásáig ne változzon semmilyen, a felhasználó által módosítható, widget tartalma a háttérben. Modalitásnak két formáját különböztetjük meg. Egyrészr˝ol – amir˝ol eddig is szó esett – a csak az applikációra vonatkozó modalitást, mely lehet˝ové teszi, hogy más applikációk ablakaihoz minden további nélkül hozzáférhetünk. Másrészr˝ol a teljes rendszerre érvényes modalitást, ahol a modális ablakon kívüli ablakokkal folytatott minden nem˝u felhasználó interakció tiltott. Ez utóbbi módszert csak a legszükségesebb esetben – már ha van ilyen – célszer˝u alkalmazni és az el˝obbi is csak akkor fogadható el felülettervezési szempontból, ha az applikáció egyéb részeihez való hozzáférés adatvesztést, vagy más komoly hibát okozna. Amennyiben mégis a modalitás mellett döntünk, ami nem ritka, hiszen az adatbevitelre, módosításra használt ablakok majd mindegyike ilyen, fontos egyértelm˝uvé tenni a felhasználó számára, hogy miként hagyhatja el azt az ablakot, ami korlátozza az applikáció más részeihez való hozzáférését. Egy ilyen menekül˝o útvonal biztosításának kézenfekv˝o módja lehet például egy Mégse feliratú gomb.
6.1.4.
Tranziencia
A dialógusok rendszerint „tranziensek” arra az ablakra, melyb˝ol származnak, azaz arra az ablakra melyen azt a m˝uveletet váltottuk ki, aminek hatására a dialógus megjelent. Ezen beállítás alapján az ablakkezel˝o képes a dialógusunkat el˝otérben, a szül˝oablak fölött tartani4 , valamint ha arra kérjük, akkor a szül˝o ablakhoz képest középen megjeleníteni (6.2.1). Ez a funkció azonban nem csak az ablakok helyes megjelenítéséhez szükséges, a megszüntetésükkor is hasznos, hiszen a destroy-with-parent tulajdonságon (property) keresztül lehet˝oség van arra utasítani a GTK-t, hogy egy ablak megsz˝unésekor azokat az ablakokat is szüntesse meg, melyek erre a szül˝oablakra nézve tranziensek. Ez leginkább akkor hasznos, hogy ha egy bizonytalan ideig létez˝o ablakra szeretnénk tranziensek lenni5 . Így nem kell tör˝odnünk azzal, hogy ablakaink esetleg „árván” maradnak.
6.2.
Használat
6.2.1.
Létrehozás
Mind a GtkDialog, mind pedig a GtkWindow típus létrehozása – már ami formai részt illeti –, teljesen hasonló az összes többi widgetéhez, van azonban egy érdemi különbség, amire érdemes kitérni. Az ablakok – igaz ez természetesen az összes többi GtkWindow típusból származó widgetre is (pl.: GtkMessageDialog, GtkAboutDialog, . . . ) – természetüknél fogva nem kerülnek bele más konténerbe, hiszen pont ezek azok a típusok, amik widgeteket tartalmaznak. Ellentétben azonban a többi típussal, ahol a referenciaszámlálás megoldja a problémát, itt a létrejött widgetek felszabadításáról magunknak kell gondoskodnunk. Paraméterek A GtkDialog létrehozásában már valamivel nagyobb a különbség, függ˝oen attól, hogy GTK+-t, vagy gtkmmet használunk, bár így sem számottev˝o. Minkét esetben meg kell adnunk a címsor szövegét, valamint azt az ablakot, amire tranziensek kívánunk lenni. GTK+ esetén – ahogy látszik – lehet˝oségünk van NULL érték megadására, ami azt jelenti, hogy nem kívánunk ezzel a lehet˝oséggel élni. A gtkmm is lehetséges ez, ha az alább látható konstruktort helyett azt hívjuk, amelyb˝ol hiányzik a parent paraméter. GtkWidget* gtk_window_new (GtkWindowTypetype);
explicitWindow (WindowTypetype = WINDOW_TOPLEVEL); classWindow(Gtk.Window): def__init__(self, type=Gtk.WindowType.TOPLEVEL, **kwds):
Kódrészlet 6.1. Window létrehozása Ahogy arról szó esett (6.1.1) a GTK két típust különböztet meg – a popup és toplevel window – melyek közül az el˝obbi olyannyira ritkán használt, hogy a C++ nyelv˝u változat esetén az alapértelmezett paramétere is van a típus konstruktorának, ahol a típus alapértelmezett értéke toplevel. 3 Ilyen
lehet például a szerkesztés menüpontok beállítások almenüjének hatására megjelen˝o ablak. beállítások esetén – ha rosszul, vagy egyáltalán nem adjuk meg a szül˝oablakot – el˝ofordulhat, hogy egy újonnan létrehozott és megjelenített dialógusunk a már létez˝o ablakok alatt, vagy között kerül megjelenítésre, ami felhasználói szempontból roppant zavaró 5 Erre lehet példa egy nem modális ablak, ami a programfutása során is megsz˝ unhet 4 Helytelen
30
A C, illetve a Python változat flags paramétere egyben tartalmazza a gtkmm modal (6.1.3) és a destroy-with-parent (6.1.4) értéket egy bitmask értékben. Ez utóbbi beállítására ugyan van lehet˝oség gtkmm esetén is, a C++ nyelvi eszközeinek korrekt használata mellett nemigen van szükség6 . GtkWidget* gtk_dialog_new_with_buttons ( constgchar *title, GtkWindow *parent, GtkDialogFlagsflags, constgchar *first_button_text, ...);
Gtk::Dialog::Dialog ( constGlib::ustring & title, Gtk::Window& parent, boolmodal = false, booluse_separator = false);
classDialog(Gtk.Dialog, Container): def__init__(self, title=None, parent=None, flags=0, buttons=None, _buttons_property=None, **kwars):
Kódrészlet 6.2. Dialog létrehozása Pozíció Egy ablak képerny˝on elfoglalt pozíciójának megadására alapvet˝oen két lehet˝oség kínálkozik. Az egyik, ha el˝ore – még az ablak megjelenítése el˝ott – megadjuk, a kívánt elhelyezkedést. Ehhez a GTK annyiban tud segítségünkre lenni, hogy választhatunk néhány el˝ore definiált elhelyezkedési pozíció közül, így nem szükséges a pixelben megadott koordináták kiszámítására id˝ot és energiát pazarolni7 . A set_position függvény még a megjelenítést megel˝oz˝oen – azaz window esetén a show, daialog esetén pedig a run meghívása el˝ott – módunkban áll az alábbi elhelyezkedési sémák közül a megfelel˝ot kiválasztani. WIN_POS_NONE Nincs befolyással a megjelenítést ablak pozíciójára nincs. WIN_POS_CENTER A megjelenítend˝o ablak a teljes képerny˝ohöz képest középen jelenik meg. WIN_POS_MOUSE A megjelenítend˝o ablak az egér aktuális pozíciója alatt jelenik meg. WIN_POS_CENTER_ALWAYS A megjelenítend˝o ablak a teljes képerny˝ohöz képest középen jelenik meg és átméretezést követ˝oen is ott marad8 . WIN_POS_CENTER_ON_PARENT A megjelenítend˝o ablak – a set_transient_for függvénnyel beállított – szül˝ojéhez képest középen jelenik meg. Ha úgy látjuk a fenti lehet˝oségek nem felelnek meg maradéktalanul céljainknak, akkor lehet˝oségünk van arra, hogy ablakunkat a kívánt pozícióra mozgassuk. Megjegyzend˝o, hogy ez a mozgatás csupán egy kérés az ablakkezel˝o felé, amit az figyelmen kívül is hagyhat. Az ablakkezel˝ok jelentékeny része ezt meg is teszi amennyiben ezzel a módszerrel kívánjuk az ablak kezdeti pozícióját meghatározni, viszont honorálja kérésünket, ha az ablak korábban már megjelenítésre került. Az ablak elhelyezkedésének megadása x, y koordinátákkal történik egy választott referenciaponthoz képest, mely lehet az ablak bármely sarokpontja, az élek középpontja és az ablak középpontja egyaránt. A mozgatás maga a move függvénnyel történik, a referenciapontot pedig a gravity értéke határoz meg. Egy ablak pozíciójának nem csak a beállítására, de lekérdezésére is szükség lehet, ugyanakkor ebben a tekintetben adott egy komoly megszorítás, amivel mindenképp szükséges számolni. A get_position függvény által visszaadott értékek a már korábban említett módon függenek egyrészr˝ol a gravity értékét˝ol, másrészr˝ol pedig az ablakkezel˝ot˝ol. Elméletben, ha a visszakapott x, illetve y értéket átadnánk a set_position függvények azt kellene tapasztalnunk, hogy az ablak egy helyben marad, gyakorlatban viszont azt tapasztalhatjuk, hogy az ablak valamennyit elmozdul. Ennek oka az ablakkezel˝o által az ablak köré rajzolt dekoráció, illetve annak geometriája, amit a GTK+ csak jó közelítéssel tud becsülni. Ez például akkor okozhat gondot, ha programunk ablakainak méretét és elhelyezkedését menteni szeretnénk, majd azt visszaállítanánk a következ˝o futtatásnál. Érdemes tehát körültekint˝onek lenni. Méret és arány A konténerek méretének meghatározásáról és elemeik (children) elhelyezkedésér˝ol leírtak (7.3) a bels˝o elrendezésük arányait átméretezéskor is megtartó ablakok kialakításakor válnak igazán fontossá. Egy ablak méretét, ha más erre vonatkozó beállítást nem teszünk – épp úgy mint mint minden más konténerét – a benne lév˝o elemek méretigény határozza meg, ugyanakkor lehet˝oség van ennek az alapértelmezés szerinti m˝uködésnek a módosítására. Egyrészr˝ol a set_default_size függvény révén, melynek megadható az ablak alapértelmezett vízszintes és függ˝oeges mérete pixelben. Ennek hatása, hogy az ablak els˝o megjelenítéskor9 legalább ilyen méret˝u lesz. Ha azonban az ablakban tárolt widgetek méretigénye azt indokolja, akkor a megadott szélesség és magasság értékeknél nagyon 6 Ha egy ablakra egy tranziens dialógust akarunk megjeleníteni, az nyugodtan lehet adattag, aminek megszüntetésér˝ ol az ablak destruktorában gondoskodhatunk. 7 Ami nem minden esetben kézenfekv˝ o feladat, hiszen adott esetben nem csak a képerny˝o felbontásával, saját ablakunk méreteivel, de a szül˝oablak, vagy éppen a desktop szélesség és magasság értékeivel is foglalkozni kell. 8 A legtöbb esetben ez a választás nem szerencsés, lévén nem feltétlenül m˝ uködik ez a mód minden ablakkezel˝o rendszer esetén. 9 Egy esetleges eltüntetéskor hide az ablak mérete úgymond mentésre kerül, azaz az alapértelmezett méret nem kerül újra alkalmazásra, ha az ablakot eltüntetjük hide, majd újra megjelenítjük.
31
méretben kerül megjelenítésre. Ezt a m˝uködést természetesen a size request megadása révén is elérhetnénk, de az alapértelmezett méret beállítása esetén a felhasználó csökkenteni tudja az ablak méretét, ha megadottak szerinti méretre nincs feltétlenül szükség. Az ablak átméretezése kapcsán két felhasználói szempontból érdemleges kérdés merül fel. Az egyik, hogy engedjük-e az ablak átméretezését a felhasználónak. Erre a kérdésre adott válasz leginkább azon múlik, hogy mennyi id˝ot kívánunk az ablak tervezésével tölteni, illetve mennyire van a felhasználónak igénye az átméretezésre. Ha id˝onk korlátos és valójában nincs szükség a méret megváltoztatására, akkor szerencsénk van, nyugodtan tilthatjuk ezt az interakció. Ha viszont ez nem lehetséges – például azért, mert az alkalmazásunk f˝oablakáról, vagy egy olyan dialógusról van szó, melyben egy lista jelenik meg, aminek hasznos lehet a lehet˝o legtöbb helyet biztosítani –, akkor id˝ot és energiát kell szálnunk az egyes widgetek viselkedésének megtervezésére. Át kell gondolnunk mely elemeknek foglalják el (expand, fill) azt a helyet, ami az átméretezés révén rendelkezésre áll majd. Egy-egy feleslegesen megnyúló widget – mondjuk egy teljes képerny˝ot elfoglaló beviteli mez˝o, amibe mondjuk csak egy IP címet szeretnénk írni – épp annyira szerencsétlenül mutat, mint amennyire zavaró az, ha hiába növeljük az ablak méretét, a lista, amib˝ol több sort szeretnénk látni, mégsem n˝o. Ha mégis az átméretezés tiltása mellett döntenénk, akkor ezt a set_resizable függvény hívásával tudjuk elérni. A másik érdemleges kérdés a programból történ˝o átméretezés, amire ugyan megoldható (resize), de er˝osen ellenjavallt usability szempontból. A fentieknél is részletesebb beállítások a set_geometry_hints függvénnyel tehet˝ok meg. Az ablak minimális és a maximális vízszintes, illetve függ˝oeges irányban külön-külön állíthatóak, ahogy az átméretezés lépésköze is, s˝ot az ablak méretarányának 10 (aspect ratio) lehetséges legkisebb és legnagyobb értéke is megadható.
6.2.2.
Minimális példa
Ennyi bevezet˝o után lássuk egy olyan példát, ami a lehet˝o legkevesebb kódsor mellet, még ha meglehet˝osen korlátozott funkcionalitás bíró, de m˝uköd˝o alkalmazást eredményez. Az alábbi C, C++, illetve Python nyelv˝u kód nem tesz egyebet, létrehoz egy ablakot, amit meg is jelenít azáltal, majd átadja a vezérlést a GTK-nak azáltal, hogy futtatja a main loopot, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#include
#include
from gi.repositoryimportGtk
int main(int argc, char *argv[]) { GtkWidget *window;
int main(int argc, char *argv[]) {
if __name__ == "__main__":
gtk_init (&argc, &argv);
Gtk::Main kit(argc, argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect(G_OBJECT(window), "delete-event", G_CALLBACK(gtk_main_quit), NULL); gtk_widget_show (window);
Gtk::Windowwindow; window.show();
window = Gtk.Window() window.connect("delete-event", Gtk.main_quit) window.show()
gtk_main();
Gtk::Main::run();
Gtk.main()
return 0;
return 0;
}
}
Kódrészlet 6.3. Minimál példa GtkWindowhoz A változatok bár egyformának t˝unnek, néhány apróságban mégis eltérnek egymástól. Ezek egy része, mint a window változó deklarálásának helye, vagy paraméterezése, a programozási nyelv sajátosságaiból következik. Mások viszont, mint a main loop futtatásának módja, illetve a függvény elnevezése már a nyelvi változat megalkotóinak belátásán m˝ulik. A programok m˝uködése azonos, csupán a technikai megvalósításban vannak eltérések. Ezekr˝ol korábban már szó esett, így itt ezeket nem részletezzük, inkább sorra vesszük miként vehetünk használatba egy frissen létrehozott ablakot, mit kell tennünk, ha elemeket szeretnénk elhelyezni az ablakban, ha vezérl˝o gombokkal szeretnénk látnánk el, majd a megjelenítést után a felhasználó interakciókat szeretnénk követni, illetve azokra reagálni.
6.2.3.
Tartalmi elemek
Egy ablak típusa szerint nem más, mint egy konténer, pontosabban fogalmazva egy GtkBin (7.1.1), amibe további elemeket tehetünk. Praktikusan ez az elem egy újabb konténer, rendszerint egy GtkBox. A GtkDialog esetén, mint azt a korábbiakban (6.1.2) kifejtettük, a konténerbe helyezett GtkBox már adott. Az így egy ablakba kerül˝o widgetekre nem csak abban az értelemben tekintünk csoportként, hogy mindegyikük szül˝oje – toplevel widget szinten – ugyanaz az ablak, de vannak bizonyos tulajdonságok, melyek ugyan konkrétan a widgetre vonatkoznak, de összefüggésben állnak az ablak más widgeteinek bizonyos tulajdonságaival, de mindig csak az azonos ablakban lév˝o más widgetekéivel, vagyis erre csoport nézve zártak. 10 szélesség/magasság
lebeg˝opontos számként adott értéke
32
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
Fókusz widget Egy adott ablakban11 egy adott pillanatban legfeljebb egy olyan widget lehet, melyen fókuszban (keyboard focus) van. Ha van ilyen widget, akkor minden – az ablak által fogadott – billenty˝uzet esemény (billenty˝u lenyomása, felengedése, . . . ) hozzá kerül továbbításra. Így érthet˝o is, hiszen ha például gépelünk valamit a billenty˝uzeten, akkor annak eredményét értelemszer˝uen csak egy beviteli mez˝oben szeretnénk látni. A widgetek többsége valamilyen látható módon is jelzi azt az állapotot, hogy aktuális fókuszban van. Ez az szövegbevitelre szolgáló widgetek (GtkEntry, GtkTextView, . . . ) esetén abban nyilvánul meg, hogy a kurzort látjuk villogni a beviteli mez˝oben, egyéb esetekben ezt egy vékony fekete keret jelzi. A fókusz egyik widgetr˝ol a másikra történ˝o mozgatására a szokások módszer, azaz a tab, illetve a kurzormozgató billenty˝uk használhatóak. Az egyes widgetek között történ˝o váltás is testre szabható a GtkContainer set_focus_chain függvényével, de erre valóban ritkán lehet szükség.
6.1. ábra. Billenty˝uzet fókusz jelzése a widgeten[6]
Az egyes widgetekre külön-külön engedhet˝o, vagy tiltható, hogy fókuszba kerülhessenek, a can-focus tulajdonság állításával. Az egyes widgettípusok esetében az alapértelmezés szerinti érték rendszrint megfelel a céljainknak12 . Ha kódból szeretnénk átmozgatni a fókuszt az egyik widgetr˝ol a másikra, vagy csak azt kívánjuk elérni, hogy az ablak megjelenítésekor legyen olyan widget ami fókuszban van, akkor a grab_focus függvényt kell alkalmaznunk, aminek el˝ofeltétele a can-focus tulajdonság igaz értéke, azaz fókuszálhatónak kell lennie, ami viszont bizonyos widgetek (pl.: GtkFrame) esetén nem lehetséges. Alapértelmezett widget esetén csaknem mindig használt tulajdonság az alapértelmezett widget. Ez ellentétben a fókusszal, ami inkább egy logikai tulajdonság, a felületre nincs, csak a m˝uködésre van hatással. Egy ablakon belül – nevéb˝ol is következ˝oen – legfeljebb egy olyan widget lehet, mely rendelkezik ezzel a tulajdonsággal, ez rendszerint a dialógus jóváhagyó (affirmative) gombja, jellemz˝oen az Ok gomb.
6.2. ábra. Gombok szokások sorrendje egy dialógusban[6]
Hatása abban áll, hogy az alapértelmezett widget – vagyis ami esetén a has-default tulajdonság értéke igaz – aktiválódik akkor, ha egy egysoros beviteli mez˝o van fókuszban (GtkEntry) és akkor Entert nyomunk, ez is csak akkor, ha az GtkEntry activates-default tulajdonsága szintén igaz érték˝u. Többsoros beviteli mez˝o (GtkTextView) esetén ez nem m˝uköd˝oképes, hiszen ott az Enter lenyomása soremelést jelent. Az alapértelmezett widgetnek a billenty˝uzetr˝ol való használat kényelmesebbé tételében van szerepe, hiszen ha minden szükséges mez˝ot kitöltöttünk egy dialógusban, akkor nem kell a megfelel˝o gombig – egérrel, vagy Tab billenty˝u(k) lenyomásával – elnavigálnunk, csak egyszer˝uen az Enter leütésére aktiválódik az alapértelmezett widget. Ahhoz, hogy egy widget egyáltalán számításba kerüljön, mint potenciális alapértelmezett widget, ahhoz el˝oször a can-default tulajdonságának kell igaznak lenni, ilyen widget több is lehet egy ablakon belül, melyek közül aktuálisan alapértelmezetté GtkWidget osztály garb_default függvényével tehetünk egyet, így annak has-default értékkel igazzá válik, míg az ablak korábbi alapértelmezett widgete, már ha volt ilyen, esetén a tulajdonság értéke természetesen hamis lesz.
6.2.4.
Vezérl˝o elemek
Amennyiben a szükséges tartalmi elemeket elhelyeztük az ablakban, a vezérl˝o elemekkel is hasonlóan kell eljárnunk. A különböz˝o célokra használt ablakok különböz˝o vezérl˝o elemeket kívánnak meg, amik az egyes típusokként er˝os hasonlóságot mutatnak. Egy f˝oablak csak kivételes esetekben tartalmaz az eddigiekben tárgyalt gombokat, a vezérlés általában menükkel, a toolbaron elhelyezett gombokkal történik (?? ábra). 11 Itt 12 Ez
ablak alatt a toplevel és nem a popup ablakokat értjük. az érték egy GtkEntry esetén igaz, míg egy Gtkabel esetén hamis alapértelmezés szerint.
33
(a) F˝oablak
(b) Dialógus
6.3. ábra. Tipikus ablakszerkezetek[6]
A f˝oablakból nyíló különböz˝o célú ablakok, melyek például egy adott elem tulajdonságainak beállítására, egy bonyolultabb funkció lépésenként történ˝o megvalósítására, az alkalmazás egészének konfigurálására, egy folyamat nyomkövetésére, esetleg a felhasználó informálására, figyelmeztetésére szolgálnak, közvetlenül, vagy közvetve a GtkDialog típusból szármáznak, saját gombsorral látjuk el o˝ ket. Tipikus példa erre egy elem tulajdonságainak szerkesztésére használt dialógus (?? ábra). GtkDialog A C, illetve a Python nyelv˝u változat mutat némi különböz˝oséget a C++ változathoz képest a gombok hozzáadásának mikéntjében. El˝obbiek esetén ugyanis több gombot is hozzáadhatunk egyszerre a dialógoshoz, egymás után sorolva a button_text és a response_id paramétereket. A paraméterlistát a C változat esetén NULL értékkel kell zárnunk, különben változatos programhibákkal fogunk szembesülni, erre a Python esetén nincs szükség, mivel itt a hívott fél tisztában van az átadott paraméterek számával. GtkWidget* gtk_dialog_add_button (GtkDialog *dialog, constgchar *button_text, gintresponse_id);
Button* Gtk::Dialog::add_button ( constGlib::ustring& button_text, intresponse_id);
defadd_button(self, text response):
void gtk_dialog_add_buttons (GtkDialog *dialog, constgchar *first_button_text, ...);
Button* Gtk::Dialog::add_button ( constGtk::StockID& stock_id, intresponse_id);
defadd_buttons(self, *args):
Kódrészlet 6.4. Gombok hozzáadása GtkDialoghoz[6] A választott nyelvi változattól függetlenül igaz, hogy a button_text paramétere vagy az általunk vágyott felirat szövege lehet, vagy egy stock ID (Ok gomb esetén például GTK_STOCK_OK).
6.4. ábra. Egy tipikus gombsor
Egy fenti elrendezés˝u – amúgy meglehet˝osen szokványos – gombsor az alábbi kódrészletekkel hozható létre az egyes nyelvek esetén. Az eltérés nem számottev˝o, nem tartalmaz semmilyen olyan különböz˝oséget, ami a korábbiakban már ne került volna ismertetésre.
34
cancel_button = gtk_dialog_add_button ( GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
cancel_button = dialog->add_button (
ok_button = gtk_dialog_add_button ( GTK_DIALOG (dialog), GTK_STOCK_OK, GTK_RESPONSE_OK);
ok_button = dialog->add_button (
gtk_widget_grab_default (ok_button);
ok_button->grab_default();
ok_button.grab_default();
help_button = gtk_dialog_add_button ( GTK_DIALOG (dialog), GTK_STOCK_HELP, GTK_RESPONSE_HELP);
help_button = dialog->add_button (
help_button = dialog.add_button(
cancel_button = dialog.add_button(
Gtk::Stock::Cancel, Gtk::Response::Cancel);
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL); ok_button = dialog.add_button(
Gtk::Stock::Ok, Gtk::Response::Ok);
Gtk.STOCK_OK, Gtk.ResponseType.OK);
Gtk::Stock::Help, Gtk::Response::Help);
Gtk.STOCK_HELP, Gtk.ResponseType.HELP);
Kódrészlet 6.5. Tipikus gombsor hozzáadása GtkDialoghoz[6]
GtkMessageDialog Létezik a GtkDialog típusnak egy – a gombok hozzáadása szempontjából érdekes sajátossággal bíró – specializált változata, melyet a felhasználóval történ˝o kommunikáció céljaira használunk, s mely ennek megfelel˝oen rendszerint csak az üzenet szövegét, illetve a válasz megadásához szükséges vezérl˝o elemeket tartalmazza.
(a) Információs üzenetablak
(b) Hiba üzenetablak
6.5. ábra. Tipikus üzenetablakok
Az ábrákon látható dialógusokat – a szövegezést˝ol eltekintve – csak típusuk különbözteti meg egymástól. A bal oldali (??) egy információs ablak, míg a jobb oldali (??) egy hibadialógus. Az egyik szembeötl˝o különbség az ablakok között az ikon, amit az üzenetablak típusa határoz meg. A fenti két típuson kívül még két saját ikonnal rendelkez˝o típus (question és warning) létezik, illetve készíthetünk ikon nélküli változatot, aminél módunk van saját ikon megadására. Az üzenetablak típus eltérése, vagy inkább specialitása a GtkDialog típushoz képest nem csak a típus megadásának lehet˝oségére korlátozódik. A kifejezetten a szoftver és a felhasználó közötti „üzenetváltás” célját szolgáló widget rendelkezik beépített elemekkel az üzenet megjelenítésére. A felhasználóval közölni kívántakat egy els˝odleges és egy másodlagos (primary-text, secondary-text) részre bonthatjuk, ahol az el˝obbi egy rövid, csak a helyzet leglényegesebb elemit tartalmazó, egy mondatos összefoglalója a közölni kívánt információnak, vagy a javasolt kívánt m˝uveletnek, míg az utóbbi ennek mélyebb, részletekbe men˝o kifejtése leírása, ami tájékoztatja a felhasználót a felmerült helyzet okairól, esetleges mellékhatásairól. Az esetek többségében a felhasználónak már az els˝odleges szöveg elolvasását követ˝oen meg kell tudni hoznia döntését, a másodlagos szöveg a döntés alátámasztására, az esetleges kétségek eloszlatására szolgál. A harmadik specialitás – a gombok felhelyezésének mikéntje – a vezérl˝o elemek szempontjából is említésre méltó. Azzal együtt, hogy dialog típusnál ismertetett módszer az örökl˝odés okán természetesen itt is használható, mivel azonban a leggyakrabban használt gombkombinációk száma er˝osen korlátos, így ezek közül létrehozáskor választhatunk. Lehetséges értékek eredményeként vagy egyedüliként a Bezárás, Ok, Mégse gombok, vagy a Ok/Cancel, Igen/Nem párosok kerülnek a dialógusra.
35
GtkWidget* gtk_message_dialog_new ( GtkWindow *parent, GtkDialogFlags flags, GtkMessageType type, GtkButtonsType buttons, constgchar *message_format, ...)G_GNUC_PRINTF (5, 6);
explicit MessageDialog ( constGlib::ustring& message, booluse_markup = false, MessageTypetype = MESSAGE_INFO, ButtonsTypebuttons = BUTTONS_OK, boolmodal = false);
GtkWidget* gtk_message_dialog_new_with_markup ( GtkWindow *parent, GtkDialogFlags flags, GtkMessageType type, GtkButtonsType buttons, constgchar *message_format, ...)G_GNUC_PRINTF (5, 6);
MessageDialog ( Gtk::Window& parent, constGlib::ustring& message, booluse_markup = false, MessageTypetype = MESSAGE_INFO, ButtonsTypebuttons = BUTTONS_OK, boolmodal = false);
def__init__(self, parent=None, flags=0, message_type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.NONE, message_format=None, **kwars):
Kódrészlet 6.6. MessageDialog létrehozása A C, illetve a Python nyelv˝u változatok, ellentétben a C++-os megvalósítással nem egyetlen paraméterként várják az üzenetablak els˝odleges szövegét, hanem egy printf-stílusú formátumleírót és az annak megfelel˝o paramétereket vesznek át. Ennek típusbiztosságáról a C változat esetén a G_GNUC_PRINTF makró gondoskodik, már amennyiben a GNU C fordítót használjuk. Ebben az esetben fordítási idej˝u figyelmeztetést kapunk ha paramétereink nem felelnek meg a formátumleíróban megadottaknak.
6.2.5.
Megjelenítés
Több lehet˝oség kínálkozik, ha egy ablakot, illetve annak tartalmát szeretnénk megjeleníteni. Használhatjuk egyrészr˝ol, a GtkWidget, a GtkWindow, illetve a GtkDialog típus által adott módszereket. GtkWidget A korábban (??) már tárgyalt show függvényt, ami megjeleníti a windowt, de csak a windowt, annak gyerekeit nem. Lévén a window egy konténer típus, helyezhetünk el további widgeteket benne, amiknek a megjelenítésér˝ol vagy már korábban gondoskodnunk kell – mondjuk egy show hívással –, vagy megtehetjük a szül˝o és az összes gyerek megjelenítését egyszerre a show_all (??) függvénnyel. GtkWindow Egy ablak esetén nem csupán a puszta megjelenítés lehet szempont, hanem az is, hogy a felhasználó az ablakot észre is vegye. Ez az estek túlnyomó többségében adott, hiszen valamilyen felhasználói interakció révén jelenik meg az új ablak. Ha viszont nem err˝ol van szó akkor szükséges lehet az megjelenítésen túl más ablakok általi takarás megszüntetésére, a tálcáról való felhozatalra, az aktuális desktopra történ˝o mozgatásra, a fókusz (6.2.3) átadására, mely m˝uveletek mind függhetnek mind a platformtól, mind az ablakkezel˝ot˝ol, mind pedig a felhasználói beállításoktól. Erre használható a present függvény. Kezeljük azonban kell˝o óvatossággal ezt a függvényt, hiszen mindannyian bosszankodtunk már egy kell˝o indok nélkül, váratlanul megjelen˝o ablak miatt. GtkDialog Egy window típusú ablak esetén – mivel jellemz˝oen nincsenek az ablakon gombok és nem modálisak – nincs igazán szükségünk arra, hogy – a kód futásának szempontjából helyben – kivárjuk a felhasználó reakciót. A dialog típus ezzel szemben általában egy felhasználói interakció révén jelenik meg (pl.: egy elem tulajdonságait, vagy az applikáció beállításait szerkeszt˝o ablak) és jelent˝os részben valamilyen döntés elé állítja a felhasználót (különösen igaz ez az üzenet ablakoknál, ahol kérést teszünk fel), melynek eredményér˝ol szeretnénk értesülni. A GtkDialog típus run függvénye – ahogy azt a következ˝oekben (6.2.7) részletesen is tárgyaljuk – pontosan ezt a célt szolgálja. Egyrészr˝ol várakozik a felhasználó interakció – amihez természetesen szükséges a main loop futtatása – majd visszatérési értékként az ablak „futtatásának” eredményét, vagyis a kiválasztott vezérl˝o elem – az ablak elkészítésekor megadott – response id-t adja vissza. Mint látható, a függvény nem kifejezetten a dialógus megjelenítését szolgálja, az hasznos mellékhatásként mégis megtörténik.
6.2.6.
Bezárás
A window tehát leginkább a bezárás kapcsán állít kihívást elénk, melyet függ˝oen attól, mit szeretnénk elérni annak hatására, hogy a felhasználó az ablak bezárását kezdeményezte, több lehetséges megoldás, az egyes megoldásokra, pedig több módszer is kínálkozik.
36
Blokkolás Kezdjük a legegyszer˝ubbnek látszó esettel, vagyis azzal, hogy semmilyen hatása ne legyen annak ha felhasználó az ablak bezárását kezdeményezi. Nyilván megfontolandó, hogy ezt tegyük, hiszen a felhasználó sem véletlenül akarja, amit akar, de ha mondjuk azt szeretnénk kikényszeríteni, hogy a f˝oablakunkból csak a Fájl menüpont „Kilépés” almenüpontjára kattintva lehessen bezárni, akkor ez egy lehetséges megoldás13 . A feladat megoldása a korábban (??) már tárgyalt delete-event szignál kezelésében rejlik. Ahogy arról szó esett ez a szignál váltódik minden ablakon (toplevel window), illetve minden az ablakban lév˝o widgeten, mikor a felhasználó az ablak bezárását kezdeményezi. A szignál két külön említésre is méltó sajátossággal is rendelkezik: 1. A szignált kezelni kívánó függvénynek egy bool értékkel kell visszatérnie, ami azt jelzi a GTK felé, hogy az adott függvény kezelte-e az eseményt, egyszersmind nincs szükség a további kezel˝o függvény meghívására. A GTK következésképp addig hívja sorra az szignálra „feliratkozott” függvényeket (signal handler) – köztük ha van ilyen, akkor az alapértelmezett szignálkezel˝o függvényt (default signal handler) amíg egyikük true értékkel nem tér vissza. 2. Ha minden ilyen függvény false értékkel tér vissza, azaz a szignál további propagálását kéri a GTK-tól, akkor konkrétan a delete-event esetében a GDK alapértelmezett eseménykezel˝oje (default event handler) fut le, ami meghívja az ablak destruktorát. Fentiek alapján ahhoz, hogy megel˝ozzük az ablak bezárását – vagyis hogy ne történjen semmi – el kell érnünk, hogy az alapértelmezett eseménykezel˝o ne hívódjon meg, azaz az általunk felkötött szignálkezel˝o true értékkel kell hogy visszatérjen, jelezve azt, hogy az eseményt kezeltük, a további szignálkezel˝o függvények meghívására nincs szükség. Ebben az esetben ez azt is jelenti, hogy az alapértelmezett eseménykezel˝o sem hívódik meg. Ehhez definiálnunk kell a szignálkezel˝o függvényeket, ami a korábbi (6.3. kódrészlet14 ) példát kiegészítve az alábbihoz hasonló módon tehetünk meg, 2 3 4 5 6 7 8 9 10 11 12
#include gboolean on_delete_event (GtkWidget *widget, GdkEvent *event, gpointeruser_data) { g_print("on_delete_event\n");
bool on_delete_event(GdkEventAny *event)
2 3 4 5 6 7 8 9 10 11 12
defon_delete_event(widget, event):
{
returnTRUE; }
std::cout<< "on_delete_event" << std::endl;
print("on_delete_event")
returntrue;
returnTrue
}
Kódrészlet 6.7. Szignálkezel˝o függvény perzisztens ablakhoz majd ezeket a függvényeket a delete-event szignálra be is kell kötnünk15 . 12 13
g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (on_delete_event), NULL);
window.signal_delete_event().connect( sigc::ptr_fun(on_delete_event));
window.connect("delete-event", on_delete_event)
Kódrészlet 6.8. Szignálkezel˝o függvény bekötése perzisztens ablakhoz Ha az adott szignál alapértelmezett szignálkezel˝o függvénnyel is rendelkezik – ami a widget osztály-leírójában kerül megadásra – és magunk akarjuk a szignált kezelni, akkor szükségessé válik, hogy a saját kezel˝o függvényünk még az alapértelmezett el˝ott hívódjék meg, ami további praktikák bevetését igényli, amir˝ol egy másik részben esett részletesebben szó. Ha a saját szignálkezel˝o függvény írását kissé túlzónak találjuk egy olyan egyszer˝u feladat ellátására, mint egy true értékkel való visszatérés, akkor nem tévedünk, s˝ot a GTK+ fejleszt˝oi is gondoltak erre és megalkották a gtk_true, illetve a gtk_false nev˝u függvényeket, melyek semmi egyebet nem tesznek, mint a nevüknek megfelel˝o értékkel térnek vissza, így a fenti példa szignálkezel˝o függvényei elhagyhatók, hiszen azok GTK+ által adott – ekvivalens funkciójú – függvényekkel helyettesíthet˝oek. g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (gtk_true), NULL);
Kódrészlet 6.9. Egyszer˝usített szignálkezel˝o függvény bekötése 13 Ne becsüljük azonban alá se a felhasználói találékonyságot, se a felhasználói környezetek változatosságát. Semmiképp ne hagyatkozzunk arra, hogy egy adott módszer a felhasználó számára véleményünk szerint nem érhet˝o el. 14 A sorszámozás az eredeti példába való beillesztés pontját mutatja. 15 A sorszámozás az eredeti példába való beillesztés pontját mutatja.
37
12 13
Eltüntetés Folytassuk másodikként azzal az eshet˝oséggel, ha el szeretnénk tüntetni az ablakot, aminek a bezárását a felhasználó kezdeményezte. Az el˝oz˝o szituációhoz hasonlóan most is több módszer adódik a feladat megoldására. A fent tárgyalt perzisztens ablakot eredményez˝o szignálkezel˝ok (6.7. kódrészlet) ismeretében a legnyilvánvalóbb megoldás, hogy még miel˝ott visszatérnénk az adott függvényekb˝ol a hide függvény segítségével eltüntetjük az adott ablakot. A megoldás jó és m˝uköd˝oképes megoldás lehet, ugyanakkor számolnunk kell azzal, hogy az ablak csak elt˝unik, de nem feltétlenül semmisül meg. A már korábban is használt minimális példa azon kiegészítését is figyelembe vesszük, ahol a delete-event szignálra a main loop futását megszakító függvényt hívjuk, akkor azt fogjuk tapasztalni, hogy az ablakunk elt˝unik ugyan, de a mögöttes m˝uködés már nagyban attól függ a két implementációt (6.7. kódrészlet) miként vontuk össze. Ha a saját szignálkezel˝o függvényünket kötjük be el˝obb a delete-event szignálra, akkor az – a visszatérési értéke révén – leállítja a szignál további kezelését, vagyis a szignálkezel˝o függvények sorának meghívását is, így az ablak a saját kezel˝o függvényünk (on_delete_event), a hide hívással kiegészítve eltünteti az ablakot, de a következ˝o szignálkezel˝o – ami kiléptetné a main loopot – már nem hívódik meg. Ha a felkötés sorrendje fordított, akkor el˝obb kilép a main loop és csak ezután hívódik meg saját függvényünk, ami egyrészr˝ol elrejti az ablakot, másrészr˝ol blokkolja a további kezelést, aminek végs˝o soron nem lehet hatása, hiszen a main loop már kilépett. Amennyiben a célunk csupán az ablak eltüntetése egyszer˝ubben is elérhetjük ugyanezt, hiszen a hide függvény közvetlenül – pontosabban egy beépített GTK+ keresztül – is beköthet˝o a delete-event szignálra. Ezt kényelmi funkciót a gtkmm esetén elveszítjük – hasonlóan az el˝oz˝o példához –, mivel az általunk használni kívánt szignálkezel˝o függvény deklarációja nem egyezik meg az el˝oírttal, így tehát ezt a „kényelmet” a típusbiztosság oltárán fel kell áldozni. g_signal_connect_swapped (G_OBJECT (window), "delete-event", G_CALLBACK (gtk_widget_hide_on_delete_event), window);
Kódrészlet 6.10. Egyszer˝usített szignálkezel˝o függvény bekötése Ha nem ragadunk le a könnyen érthet˝o, ám nem túl életszer˝u minimális példánknál, akkor az mondható el, hogy ablakokat – legyenek azok GtkWindow, vagy GtkDialog típusúak –, valamilyen felhasználói interakcióra reagálva hozunk fel, valamilyen kezel˝o függvényben. Az bezáráskori elrejtés akkor lehet hasznos számunkra, ha nem akarjuk újra és újra elkészíteni az ablakot az adott felhasználói m˝uveletre. Erre lehet példa mondjuk egy névjegy (about) ablak, aminek a tartalma nem változik a program futása során, így azt az alkalmazás indulásakor, vagy az els˝o megtekintéskor létrehozzuk, utána már elegend˝o csak elrejteni, vagy újra megjeleníteni. Másik példa lehet egy státusz jelleg˝u információkat megjelenít˝o ablak, amiben úgymond gy˝ujteni tudjuk az adatokat és ha a felhasználó be is zárja az ablakot, mi az elrejtés után az adatgy˝ujtést és az ablak tartalmának frissítését tovább fojtatjuk, majd az újbóli megjelenítéskor már a naprakész információk jelennek meg. Megszüntetés Mint az a fentiekb˝ol már kiderült – függetlenül attól, hogy GtkWindow, GtkDialog, vagy ezekb˝ol származó típusokról van-e szó –, a delete-event szignál kiváltódását – ha egyebet nem teszünk – az ablak destruktorának meghívás fogja automatikusan követni, ami az esetek túlnyomó többségében meg is felel a céljainknak.
6.2.7.
Eseménykezelés
Szinkron A GtkDialog és a GtkWindow típusok közötti eltérések (6.2.4) közül a legszámottev˝obb – mivel ehhez tartozik a legtöbb beépített szolgáltatás –, a vezérl˝o elemek, azaz a gombok kezelése. A GtkDialog nem csupán arra ad lehet˝oséget, hogy a gombokat egy erre a célra készült konténerbe helyezzük el – ezt magunk is megtehetnénk minden különösebb er˝ofeszítés nélkül –, hanem az ezeken végzett felhasználói interakciókat is egyszer˝uen nyomon követhetjük. Választhatunk az adott (kód)környezetben számunkra kényelmesebb – szinkron, illetve aszinkron eseménykezelés közül. El˝obbi esetén helyben16 tudjuk kezelni az eseményeket, utóbbi viszont eseménykezel˝o függvények megírását és bekötését teszi szükségessé. 16 Az
ablak létrehozásának helyén.
38
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include
#include
from gi.repositoryimportGtk
int main(int argc, char *argv[]) { GtkWidget *dialog;
int main(int argc, char *argv[]) {
if __name__ == "__main__":
gtk_init (&argc, &argv);
Gtk::Main kit(argc, argv);
dialog = gtk_dialog_new();
Gtk::Dialogdialog;
dialog = Gtk.Dialog()
gtk_dialog_run (GTK_DIALOG (dialog));
dialog.run();
dialog.run()
return 0;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
return 0;
}
}
Kódrészlet 6.11. Minimál példa GtkDialoghoz A run függvény (12.) egyrészr˝ol függvényeket köt be a szükséges szignálokra (response, unmap, delete-event, destroy), majd egy saját main loopot futtatásán belül kezeli az említett eseményeket. Ha ezek közül bármelyik bekövetkezik, akkor a main loop futása megszakad, és a run függvény a megfelel˝o értékkel visszatér. Ennek kezelése tipikusan egy if, vagy egy switch szerkezeten belül történik. Ha a fenti minimális példát a korábbi, gombok hozzáadását tartalmazó forrással (6.5. kódrészlet) egészítjük ki17 , mondjuk az alábbihoz hasonló módon kezelhetjük a felhasználói döntés eredményeként kapott választ (response). 12 13 14 15 16 17 18 19 20 21 22
gintresult = gtk_dialog_run (GTK_DIALOG (dialog)); switch (result) { caseGTK_RESPONSE_OK: do_application_specific_something(); break; default: do_nothing_since_dialog_was_cancelled(); break; } gtk_widget_destroy (dialog);
intresult = dialog.run(); switch (result) { case Gtk::RESPONSE_OK: do_application_specific_something(); break; default: do_nothing_since_dialog_was_cancelled(); break; }
result = dialog.run() if result == Gtk.ResponseType.OK: do_application_specific_something() else: do_nothing_since_dialog_was_cancelled();
Kódrészlet 6.12. Minimál példa GtkDialoghoz Itt az egyszer˝uség kedvéért csak az Ok, illetve a többi gomb kerültek megkülönböztetésre, így ha a Mégse, illetve a Súgó gomb aktiválódott, akkor is a switch szerkezet default ága (18) fut le. Kérdés azonban, hogy ha az imént megismert delete-event szignál váltódik ki az ablak bezáródásának hatására, annak mi lesz az eredménye. A válasz egyszer˝u, hiszen a switch els˝o ágára (15) nem futhatunk rá, tehát marad itt is a default ág. Ha külön szeretnénk kezelni ezt az esetet, akkor a RESPONSE_DELETE_EVENT konstans kezelésére kell egy új ágat beillesztenünk. Nem minden esetet fedtünk azonban le, elképzelhet˝o ugyanis, hogy a dialóg felszabadul, míg a run függvény fut. Ebben az esetben a visszatérési érték RESPONSE_NONE lesz, így ezt az esetet is meg lehet különböztetni a többit˝ol, azonban mégsem tanácsos. Helyette, ha mindenképpen meg akarjuk szakítani a run futását, akkor a resposne függvényt hívhatjuk, aminek eredményeként a run a resposne-nak paraméterként átadott értékkel tér vissza. Az élelmesebbek megfigyelhetik, hogy dialog példaprogramból (6.11. kódrészlet) a window hasonló példájához (6.3. kódrészlet) kimaradt a show függvény hívása, ami annak tudható be, hogy a run ezt megteszi helyettünk, ahogy a dialógusunkat is modálissá teszi a run futásának idejére. Aszinkron Ha valamilyen oknál fogva lemondanák a run adta kényelemr˝ol, lehet˝oségünk van az aszinkron kezelésre. Ebben az esetben a run helyett a show függvényt hívjuk, illetve egy saját függvényt – melyben elvégezzük a számunkra szükséges m˝uveleteket – kötünk be a response szignálra. Ezt a módszert használva ebben a függvényben kell gondoskodnunk az ablak felszabadításáról is, már amennyiben nem csak a delete-event esemény hatására szeretnénk, hogy ez megtörténjen. A response szignál paraméterei között a response_id is szerepel, így a függvény tartalma hasonló lehet a run hívást követ˝o kódhoz (6.12. kódrészlet). M˝uködésben azonban van némi különbség, hiszen a delete-event alapértelmezett m˝uködésének megváltoztatására például nincs mód. Az aszinkron a megoldásra lehet egy másik példa, ha a lehetséges választások mindegyike ugyanazzal az eredménnyel kell járjon, például be kell záródjon az ablak. Ennek olyan leginkább helyzetekben van realitása, ahol csupán egy (pl.: Bezárás) gomb jelenik meg az ablakon. Erre lehet példa egy olyan üzenetablak (MessageDialog), amiben nem egy kérdést teszünk fel, hanem csupán informáljuk a felhasználót. g_signal_connect_swapped (G_OBJECT (dialog), "response", G_CALLBACK (gtk_widget_destroy), dialog);
Kódrészlet 6.13. Egyszer˝usített response-kezel˝o függvény bekötése 17 A
változók deklarációinak hozzáadása szükséges a fordíthatóság érdekében.
39
12 13 14 15 16 17 18 19 20 21 22
6.2.8.
Saját eseménykezel˝o
Ha a fentiekben vázoltak valamilyen oknál fogva nem elégítenék ki igényeinket, vagy már egyébként is alkalmasabb módszernek látszik egy saját widget implementálása akkor nem kell egyebet tennünk, minthogy az o˝ sosztály eseménykezel˝o függvényét felüldefiniáljuk a nyelvi változatnak megfelel˝o módon. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
staticgboolean my_window_delete_event (GtkWidget *widget, GdkEventAny *event) { returnTRUE; }
classMyWindow(Gtk.Window): classMyWindow : publicGtk::Window { virtualboolon_delete_event (GdkEventAny* event) defon_delete_event(self, widget, event): Gtk.main_quit() { returntrue; } };
staticvoid my_window_class_init (MyWindowClass *class) { GtkWidgetClass *widget_class; widget_class = (GtkWidgetClass*) class; widget_class->delete_event = my_window_delete_event; }
Kódrészlet 6.14. Eseménykezel˝o felüldefiniálása saját widgetosztályban Az objektum-orientált megvalósítások esetén ez egy lényegesen kisebb er˝ofeszítést igényl˝o feladat. Ahogy ezt a fenti kódrészlet is mutatja a C változatból épp csak a leglényesebb rész emelhet˝o ki egy jó tucatnyi sorban18 , addig a csaknem teljes érték˝u C++, Python kódok ennek a felét sem teszi ki.
6.3.
Platformfügg˝o sajátosságok
Bár a GTK – a GDK 19 , illetve a Glib függvénykönyvtárakon keresztül – komoly platformfüggetlenséget biztosít, mégis számolnunk kell a különbségekkel, különösen az ablakok kezelése kapcsán. Ezek közül itt csak a két legfontosabbat emeljük ki.
6.3.1.
Ablakkezel˝o
Bizonyos értelemben maga a az ablakkezel˝o is egy platform, hisz ahogy az operációs rendszerek a t˝olük elvárt funkciókat – mint amilyen például a fájlkezelés – a maguk módján valósítják meg, úgy az ablakkezel˝o rendszerek is saját szisztémájuk szerint teszik ezt. Bizonyos funkciók csak néhány ablakkezel˝o implementál, addig másokat csaknem minden ilyen rendszer megvalósít, bár arra nem számíthatunk, hogy az egyes megvalósítások minden részletben megegyeznek. Az ablakkezel˝ok különböz˝oségei fejlesztési oldalról azzal a következménnyel járnak, hogy még akkor is szembe kell néznünk a platformok sajátosságai által okozott nehézségekkel, ha egyébként alkalmazásunkat nem használjuk több különböz˝o20 operációs rendszer alatt. Még az olyan egyszer˝u és széles körben megvalósított funkciók kapcsán, mint amilyen maximalizálás, vagy a minimalizálás (maximize, iconify) kételkednünk kell a mögöttes implementáció meglétében, illetve figyelembe kell vennünk az egyes megvalósítások különböz˝oségeit, vagyis nem alapozhatunk ezen függvények meghívását követ˝oen arra, hogy az ablak abba az állapotba kerül, amire számítottunk. Akár az el˝obbiek említett funkciókra, akár mondjuk az ablak el˝otérben tartására (keep_above), akár teljes képerny˝oméretre nagyításra (fullscreen), akár az összes munkaterület (desktop) való egyszerre történ˝o meg megjelenítésre (stick) kérjük az ablakkezel˝ot, nem bizonyos, hogy kérésünk teljesül. Ennek csak az egyik oka az, hogy az ablakkezel˝o nem támogatja az adott funkciót, a másik pedig, hogy kérésünket követ˝oen valaki más az el˝oz˝o, vagy éppen mindkett˝ot˝ol eltér˝o állapot állít be. Ennél fogva ügyelnünk kell arra, hogy egy ilyen helyzetre programunk fel legyen készülve. Az ablak dekorációja a másik olyan terület, ahol kéréseket (hint) intézhetünk az ablakkezel˝o felé. Hasonlóan azonban a fent tárgyaltakhoz itt is igaz, hogy célszer˝u ellen˝orizni feltételezéseinket arra vonatkozólag, hogy kérésünk úgy és abban a formában hajtódik végre, ahogy azt mi elgondoltuk. Számos olyan eset lehetséges az ablak dekorációján található bezáró gomb megjelenítésének tiltásától (deletable), az ablak taskbarból történ˝o kihagyásáig (skip_taskbar_hint) melynek implementációs részletei teljes egészében az ablakkezel˝ot˝ol függenek.
6.3.2.
Vezérl˝o elemek
A GTK alapértelmezés szerint a gombokat a GNOME Human Interface Guideline[6] (HIG) által javasolt elrendezését alkalmazza, vagyis a jóváhagyó (affirmative) gomb a jobb oldalon a széls˝o pozícióba kerül, míg a menekül˝o gomb (cancel) ett˝ol balra kerül. Ha eltér˝o – úgymond alternatív elrendezést – szeretnénk, akkor azt a set_alternative_button_order függvény segítségével egy korábbi példát (6.5. kódrészlet) kiegészítve az alábbi módon állíthatjuk be. 18 A
teljes C kód egy ?? soros .c, illetve egy ?? soros .h állományból áll. Drawing Kit 20 Ebben a tekintetben a Linux alapú rendszereket közel azonosnak tekinthetjük 19 GIMP
40
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
gtk_dialog_set_alternative_button_order ( GTK_DIALOG (dialog), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, GTK_RESPONSE_HELP, -1);
dialog.set_alternative_button_order_from_array( intalt_resp[] = {Gtk::RESPONSE_OK, Gtk::RESPONSE_CANCEL, Gtk::RESPONSE_HELP}; size_talt_resp_size = sizeof(alt_resp) / sizeof(int); std::vector alt_resp_vector (alt_resp, alt_resp + alt_resp_size); dialog->set_alternative_button_order_from_array (alt_resp_vector);
[Gtk.ResponseType.OK, Gtk.ResponseType.CANCEL, Gtk.ResponseType.HELP] )
Kódrészlet 6.15. Alternatív gombsorrend beállítása Amennyiben az üzenetablakoknál megismert (6.2.4) páros gombok (Ok/Cancel, Igen/Nem) valamelyikét használjuk, akkor ezt a sorrendállítást a GTK megteszi helyettünk.
6.4.
A kód
6.4.1.
Fordítás és linkelés
A korábbiakhoz hasonlóan az alábbi parancssorok segítségével fordíthatóak programjaink. gcc gtk_sourcefile.c -o gtk_binary ‘pkg-config --cflags --libs gtk+-3.0‘ g++ gtkmm_sourcefile.cc -o gtkmm_binary ‘pkg-config --cflags --libs gtkmm-3.0‘
6.4.2.
Futtatás
A futtatással ezúttal is a forrásfájlok – egyszersmind a fordítás – könyvtárában érdemes próbálkoznunk, a példaprogram nevét˝ol függ˝oen a ./futtatható_bináris parancs kiadásával.
6.4.3.
Eredmény
Bármily hihetetlen ezúttal sem történik semmi egyéb, mint a korábbiakban. Remélhet˝oleg azonban a különbség mégis érzékelhet˝o annyiban, hogy legutóbb a meglepetéssel teli borzongást ablakunk váratlan felbukkanása, míg most a bennünk szikraként felvillanó megértés okozza.
6.5.
Tesztelés
6.5.1.
Keresés
Applikáció keresése Fejlesztés közben – már amennyiben a felhasználói felületet kódból és nem egy felülettervez˝o program segítségével hozzuk létre – az egyes widgetek létrehozáskor módunkban áll egyúttal valamilyen változóval hivatkoznunk rájuk, így a kés˝obbiekben nincs szükségünk arra, hogy az egyes widgeteket a felhasználói felületen belül keresgessük. Teszteléskor ugyanakkor a felületet készen kapjuk így az els˝o feladat azon elemek megtalálása, amiken kés˝obb valamilyen m˝uveleteket szeretnénk végezni. classGtkDemoTest(unittest.TestCase): defsetUp(self): fromdogtailimportutils self.pid = utils.run(’gtk3-demo’) self.app = procedural.focus.application(’gtk3-demo’)
classGtkDemoTest(unittest.TestCase): defsetUp(self): fromdogtailimportutils self.pid = utils.run(’gtk3-demo’) self.app = tree.root.application(’gtk3-demo’)
Kódrészlet 6.16. Alapvet˝o elemek keresése teszt tree, illetve procedural API használata mellett Ahogy az a korábbi minimális tesztelési példában is látszott az els˝o lépés a tesztelés során, hogy a kívánt applikációt megtaláljuk a sok egyéb futó alkalmazás között. Ha ezzel megvagyunk, akkor az applikáción belül kereshetjük meg annak ablakait, azokon belül pedig az egyes widgeteket. El˝obbire a célra szolgál a Dogtail tree moduljának application függvénye. A függvény mindössze egy paraméter vesz át, a keresett applikáció nevét. Ez a név jellemz˝oen – bár nem minden esetben – megegyezik annak az applikáció indításához futtatott állomány nevével. Mint a kés˝obbekben is csaknem mindig, a kísérletezés segít leginkább az applikáció felderítésében, a kereséshez használandó nevek meghatározásában. Ehhez egyrészr˝ol használhatjuk a korábban már említett Accercisert, ami voltaképpen egy grafikus felhasználói felület, ami az akadálymentesítéhez használt API által megszerezhet˝o információkat jeleníti meg struktúrált formában. Másrészr˝ol használhatjuk a Dogtailt, a konkrét esetben a Root osztály applications függvényét, ami az összes – az accessibilty alrendszer számára látható – applikációval tér vissza.
41
Az application függvény a tree modul Application osztályának egy, az applikációhoz tartozó példányával tér vissza, vagy ha az applikációt az újrapróbálkozásokat követ˝oen – melyek számát a Config osztály searchCutoffCount tulajdonsága, míg a próbálkozások között tartandó szünet mértékét ugyanezen osztály searchBackoffDuration tulajdonsága határoz meg – nem találja SearchError kivételt dob. Általános keresés A findChild függvény els˝o paramétere egy feltételhalmaz (predicate). Amennyiben a keresés során ennek a faltételhalmaznak megfelel˝o elemet találunk a közvetlen vagy közvetett leszármazottak között – függ˝oen a recursive paraméter értékét˝ol –, akkor a függvény az els˝o ilyennel visszatér. Amennyiben nem talál megfelel˝o elemet és a retry paraméter értéke True, akkor újra próbálkozik a config objektumban foglalt beállításoknak megfelel˝oen. Amennyiben a retry értéke False értelem szer˝uen összesen egy próbálkozás történik. Ha a requireResult paraméter értéke True, akkor SearchError kivételt dob, ha nem akkor egyszer˝uen None értékkel tér vissza. Megjegyzend˝o, hogy mindkét paraméter alapértelmezett értéke True, azaz a Dogtail többször is próbálkozik és sikertelenség esetén SearchError kivételt dob. classGenericPredicate(Predicate): def__init__(self, name = None, roleName = None, description= None, label = None, debugName=None):
Kódrészlet 6.17. Node keresése GenericPredicate, illetve findChild függvény segítségével Ahogy látszik a findChild függvénynek átadott GenericPredicate objektum voltaképpen a keresési feltételeket zárja egy egységbe. Ezek a feltételek a név (name), a szerep neve (roleName) , leírás (description), illetve felirat (label). A m˝uködésr˝ol fontos megjegyezni, hogy a feltételhalmazban az utolsó feltétel (label) el˝onyt élvez a többivel szemben, vagyis amennyiben ezt feltételt megadjuk a másik három nem érvényesül. Ha viszont csak az els˝o három paraméter használjuk azok egymással és kapcsolatba kerülnek, vagyis csak olyan elem lehet a keresés eredmény, ami minden feltételnek megfelel. A feltételhalmazban megadott paraméterek értékeinek kísérleti úton meghatározását a már több alkalommal említett Accerciser alkalmazás tud segíteni. Egyrészr˝ol megjeleníti az egyes applikációkat, azok ablakait, illetve a további gyerekelemeket fa hierarchiába szervezve. Másrészr˝ol az egyes – amik mind egy-egy Node típusú objektumot jelentenek – kapcsán megmutatja mik az objektum tulajdonsági, állapotai, a rajta végrehajtható akciók. Természetesen a kés˝obbiekben az egyes widgettípusok ismertetésekor ezen értékekre mi is ki fogunk térni. Az általános keresés szempontjából az imént említett néhány paraméter, illetve a Node osztály ezeknek megfelel˝o paraméterei érdemlegesek. A név attribútum (name) a widget típusától – amit a role, illetve annak szöveges formája a roleName reprezentál –, függ˝oen vesz fel értéket. Ablakok esetén annak címsorát, címkék esetén azok szövegét, olyan widgetek esetén, amikhez címkét kapcsoltunk szintén a címke szövegét. A nevek felvett értékeinek részleteire, valamint a típusnevekre az egyes widgettípusok tárgyalásakor térünk. Maga a név explicit módon is beállítható a GTK, pontosabban az ATK révén, hiszen ez utóbbi interfészen keresztül történik az accessibilty réteggel történ˝o kapcsolattartás. Minden GtkWidget objektumhoz tartozik egy AtkObject objektum, amit a get_accessible függvény segítségével kérhetünk. Az AtkObject a set_name függvény révén adható olyan név, amire a Dogtail használata során is hivatkozni tudunk. A már említett Predicate osztálynak példányait felhasználhatjuk arra is, hogy csupán annyit tudjuk egy adott Node gyerekeként, ami megfelel a predicate által megfogalmazott feltételhalmaznak. Ehhez a Predicate osztály satisfiedByNode függvényét kell hívnunk, arra azzal a Node objektummal paraméterként, amire a vizsgálatot el szeretnénk végezni. Egy adott Node objektumról ugyanez a döntés a satisfies függvény hívásával hozható meg aminek viszont a predicate a paramétere. Specializált keresés Létezik számos specializációja, leszármazottja a Predicate osztálynak, amik a gyakran használt keresések egyszer˝usítésére szolgálnak. Ezek közül minden részben azokat vesszük sorra, amik az adott rész szempontjából érdekesek. Itt tehát az applikációk és ablakok keresésére szolgálok származtatásokat ismertetjük. Applikáció. A Root osztály application függvénye az Application osztály a tree modul Node osztályából származik, ami gyakorlatilag minden olyan osztálynak o˝ se, melyet egy keresés eredményeként visszakaphatunk (Application, Root, Window). defapplication(self, appName): returnroot.findChild(predicate.IsAnApplicationNamed(appName), recursive=False)
Kódrészlet 6.18. Applikáció keresése application függvénnyel, illetve IsAnApplicationNamed objektummal Ahogy látszik az application függvény nem más, mint egy specializáció, ami tulajdonképpen Node osztály findChild általános keres˝ofüggvényét hívja paraméterként egy IsAnApplicationNamed osztály egy objektumával, ami a Predicate osztályból származik és példányosításkor a keresend˝o applikáció nevét veszi át paraméterként.
42
Ablak. Hasonlóan az applikáció kereséséhez az ablakok keresésére is létezik a Predicate osztályból származó saját osztály. Mind a IsAWindowNamed, mind a IsADialogNamed konstruálásához csak az ablak címsorát kell megadnunk. A két külön osztályra mindössze azért van szükség, mert a GtkWindow és a GtkDialog típusok a Dogtail reprezentáció más roleName értékkel rendelkeznek (frame, dialog), ahogy az a egyezést vizsgáló függvénnyek implementációjából is látszik. classIsAWindowNamed(Predicate): def__init__(self, windowName): ... def_genCompareFunc(self): defsatisfiedByNode(node): returnnode.roleName==’frame’ andstringMatches(self.windowName, node.name) returnsatisfiedByNode classIsADialogNamed(Predicate): def__init__(self, dialogName): ... def_genCompareFunc(self): defsatisfiedByNode(node): returnnode.roleName==’dialog’ andstringMatches(self.dialogName, node.name) returnsatisfiedByNode
Kódrészlet 6.19. Ablak keresése specializált Predicate objektummal
6.5.2.
Státuszok
Az ablakok kapcsán – függetlenül attól, hogy GtkWindow, vagy GtkDialog típusról van szó – van néhány tulajdonság, amit ebben a részben a fejlesztési oldalról már tárgyaltunk és most a visszaellen˝orzésükre is kitérünk. Az státuszok alapvet˝oen két érték˝uek és mint ilyenek egy állapot halmazt alkotnak, ami egy adott Node objektumra egy adott pillanatban jellemz˝o. Ez az állapot-, vagy státuszhalmaz a getState függvény segítségével kérdezhet˝o le, majd ezt kell megvizsgálnunk, hogy a számunkra aktuálisan érdekes állapot része-e a halmaznak. @property defstatename(self): returnself.getState().contains(pyatspi.STATE_STATENAME)
Kódrészlet 6.20. Node státuszvizsgálatához szükséges függvény sémája Létezik néhány olyan – a kés˝obbiekben részletezett – státusz, amikre a Node osztály implementálja a megfelel˝o lekérdez˝o függvényt, azon esetekben azonban, ahol ez nem áll rendelkezésre magunknak kell a megoldásról gondoskodnunk. Az ablakok szempontjából az átméretezhet˝oség (resizable), a modalitás (modal), valamint az az állapot, hogy a vizsgált ablak aktuálisan az aktív ablak (active) ablak-e az érdemleges állapotok.
6.5.3.
Interfészek
A GTK koncepcióját taglaló részben esett néhány szó róla, a GTK akadálymentesítéhez szükséges implementációt az ATK által definiált interfésznek megfelel˝oen nyújtja. A Dogtail voltaképpen egy Python nyelven íródott absztrakció ezen réteg fölé, ami elrejti annak részleteit és egy magasabb szinten, a felhasználói felületek teszteléséhez megfelel˝o módon kezeli az akadálymentesítési réteg által nyújtott funkcionalitásokat. Komponens Az egyik ATK által definiált interfész a AtkComponent, aminek segítségével az egyes objektumok pozíciójáról, illetve méretér˝ol szerezhetünk információkat. A Dogtail ezen interfész részleteit elrejti elölünk, kihasználva a Python nyelv adottságait egy-egy tulajdonság (property) kiolvasásával férhetünk hozzá az AtkComponent osztály által szolgáltatott értékekhez. Ezek az értékek a Node pozíciója (position), mérete (size), illetve ezek összességét jelent˝o kiterjedés (extents), ami mind az x, y, mind pedig a szélesség, magasság értékeket tartalmazza. Bár ezek az értékek minden Node esetén elérhet˝oek, az ablakokon kívül – ahol ezen értékek révén ellen˝orizni tudjuk az alapértelmezett méretet, illetve a szül˝o ablakhoz képesti pozíciót –, különösebb jelent˝oségük nincs. Akciók Vannak esetek, amikor sem nem információkat kinyerni, sem nem információkat bevinni nem akarunk a tesztelend˝o alkalmazásba, ehelyett inkább a mozgásban szeretnénk azt tartani, bizonyos akciók révén. Példának okáért ilyen lehet egy gomb megnyomása, ami tovább mozdítja a tesztelend˝o alkalmazást. A végrehajtható akciókat, illetve azok kezelését az AtkAction interfész fogja össze. Ezt az interfészt a queryAction függvény segítségével kérdezhet˝o le.
43
Az interfész abban nyújt segítséget, hogy az nActions attribútumból az adott elemen keresztül kiolvashatjuk a végrehajtható akciók számát, bár ez következik az akciókat tartalmazó dict (actions) számosságából is, ami az akciók neveihez magukat az akciókat reprezentáló osztályok (dogtail.tree.Action példányait rendeli. Ezek tartalmazzák a az akció nevét (name), leírását (description) attribútumként, illetve egy paraméter nélküli függvényt (do), ami révén az akciót végrehajthatjuk. Ez utóbbi a Node osztály doActionNamed függvénye révén is megtehet˝o, ha ismerjük az akció nevét, mivel ez a függvénynek átadandó egyetlen paraméter. A függvények visszatérési értéke, hogy az akciót sikerült-e végrehajtani. A GtButton típus objektumaira, azaz jelen esetben a dialógusok kapcsán tárgyalt gombokra igaz, hogy végrehajtható rajtuk a click akció, ami a gombra való kattintás kiváltódását eredményezi. Ennek hasznát természetesen akkor látjuk, amikor egy dialógus ablak beviteli mez˝oinek kitöltésével végeztünk és szeretnénk a normál ügymenetnek megfelel˝oen az Ok gombot megnyomni, ekkor használhatjuk a doActionNamed(’click’), vagy a do(’click’) függvényhívást függ˝oen attól, hogy a gombhoz tartozó Node, vagy annak action interfésze áll rendelkezésünkre.
6.5.4.
Tulajdonságok
Azon információk számára, amik sem a különböz˝o ATK által definiált interfészeken, sem az státuszok révén, sem más módokon nem érhet˝oek el, adott egy név érték párokat tartalmazó adatszerkezet. Az adatszerkezet lekérdezhet˝o dict (get_attributes), illetve list (getAttributes) formájában is, ahol a a lista elemei a nevek és az értékek szöveges változatainak kett˝osponttal (:) elválasztott értékei, míg a szótár értelemszer˝uen a neveket kulcsként, az értékeket a kulcsokhoz rendelt értékként tartalmazza. Az attribútumlista minden Node esetén tartalmazza annak a grafikus eszközkészletnek nevét, aminek révén a tesztelt alkalmazást létrehozták. A GTK esetén tehát a toolkit névhez nem meglep˝o módon a gtk érték párosul. Egyébiránt az eszközkészlet neve elérhet˝o a Node toolkitName nev˝u tulajdonságán keresztül, ehhez hasonlóan az eszközkészlet verziója pedig a toolkitVersion tulajdonságon keresztül.
44
7. fejezet
Konténerek Már a legegyszer˝ubb felhasználói felület láttán is könnyen belátható, hogy a korábbi példaprogramjaink kissé sántítanak, mégpedig abban a tekintetben, hogy nincs olyan valós életben is használt program amiben egy-egy widget lenne csupán. Ha viszont több widgetet szeretnénk elhelyezni egy ablakban kézenfekv˝o kérdés, hogy miként tudnák o˝ ket a felületen csoportokba rendezni. Erre a kérdésre keressük a választ ebben a részben.
7.1.
Fogalmak
A korábbiakhoz hasonlóan itt is érdemes el˝oször tisztázni néhány alapfogalmat és csak utána kezdeni bele az érdemi munkába.
7.1.1.
Konténerek
A widgetek a felületen történ˝o csoportokba csoportokba rendezése konténerek (container) segítségével valósul meg. Ezek olyan láthatatlan widgetek, melyekbe más widgeteket helyezhetünk (pack). A GtkContainer egy önmagában nem használható absztrakt osztály, ami csupán o˝ sül szolgál minden olyan származtatott osztálynak, melyet widgetek tárolására lehet használni. Alapvet˝oen két olyan o˝ sosztállyal találkozhatunk a kés˝obbiekben, melyek további származtatás alapjául szolgálnak. Ezek a GtkBin és a GtkBox. A GtkBin ráadásul absztrakt osztály, azaz csak további származtatás céljára használható, példányosítani nem lehet. Bár a GtkBin és a GtkBox osztályok csupán a bennük tárolható elemek számosságában különböznek egymástól, szerepük mégis gyökeresen eltér˝o. El˝obbi mindössze egy elem tárolására alkalmas, vagyis nem klasszikus tárolóként használatos, hanem rendszerint valamilyen dekorátor funkciót ad hozzá a benne tárolt elemhez (pl: GtkWindow, GtkFrame, GtkButton, . . . ). Utóbbi konténerek a widgetek vízszintesen, illetve függ˝oleges rendezett tárolását teszik lehet˝ové. Egy elemu˝ konténerek A GtkBin jelent˝osége a további származtatásoknál jelentkezik majd, hisz az olyan nélkülözhetetlen típusoknak, mint az ablak (GtkWindow), a gomb (GtkButton), vagy a frame (GtkFrame) mind a GtkBin az o˝ sosztálya. Több elemu˝ konténerek A felületi elrendezés kialakításakor játszanak fontos szerepet, a bennük található widgetek – amit a GTK konténer gyerekeknek (children) nevez – elrendezésén túl azok méretét és konténeren belüli pozícióját is meghatározza. Ilyen típusok például maga a GtkBox, vagy a táblázatos megjelenítést szolgáló GtkGrid.
7.1.2.
Méretezés
A GtkContainer osztály legfontosabb funkcionalitása – amit a származtatott osztályok is felhasználnak – az, hogy meg tudja határozni a benne található elemek méretét. Ezt persze nem teljesen önállóan teszi, hanem megkérdezni a benne található widgeteket, hogy mekkora helyre lenne szükségük. Minden egyes widget saját hatáskörben állapíthatja meg, hogy mekkora az a vízszintes, illetve függ˝oleges kiterjedés, ami az igényeinek legjobban megfelelne, illetve amire minimálisan szüksége van. Ez a mechanizmus fentr˝ol lefelé, azaz a gyökért˝ol a levelek felé terjed abban a fa hierarchiában, melynek gyökere az ablak, közbüls˝o elemeit a konténerek, leveleit pedig a widgetek alkotják. A legegyszer˝ubb módszer nyilván az, hogy a konténerek – amilyen maga az ablak is – összeadják a gyermekeik (a fában közvetlenül alattuk lév˝o elemek) méretigényét és azt sajátjukként propagálják. A m˝uködési modell azonban nem ilyen egyszer˝u, ugyanis az egyes widgetek vertikális helyigénye változhat annak függvényében, hogy mennyi hely 45
áll rendelkezésre horizontálisan. Kézenfekv˝o példa erre egy többsoros címke, ami a vízszintes hely növekedése esetén kevesebb sorban – azaz kisebb függ˝oleges helyen – is elfér. Az egyes widgetek végleges méretének meghatározása ennek okán több ütemben történik az alábbiak szerint. A konténer el˝oször lekérdezi közvetlen gyermekei által preferált minimális (minimal) és szokványos (natural) szélességigényét (size request), majd ezt összegezve adja tovább, mint saját vízszintes helyigényét. Ezt követ˝oen a minimális horizontális helyigényt, mint rendelkezésre álló helyet megadva a konténer lekérdezi gyermekei függ˝oleges helyigényét. Ezzel meghatározásra került a minimális vízszintes, illetve az ahhoz tartozó függ˝oleges helyigény, aminél kisebb helyen az adott konténer nem fér el. Ezt követ˝oen a legfels˝o szint˝u konténer, vagyis maga az ablak alapértelmezett, vagy a minimális mérte alapján – ha az utóbbi nagyobb az el˝obbinél – megfelel˝oen újrakezdi a függ˝oleges helyigén lekérdezését. Ezek után a konténer a szétosztja a rendelkezésre álló vízszintes helyet az általa tartalmazott widgetek között, majd ennek megfelel˝oen újra megkezd˝odik a függ˝oleges helyigény lekérdezése. Néhány ciklust követ˝oen adott az egyes widgetek a helyzethez szabott helyigénye.
7.1.3.
Elrendezés
A legtöbb esetben egy ablak több helyet tud rendelkezésre tudja bocsájtani mint, amit a benne lév˝o widgetek igényeltek. A kérdés tehát abban áll, hogy mi legyen akkor miként jelenítse meg a konténer a saját widgeteit, ha több a hely, mint amennyire feltétlenül szükség volna. Ilyen eset például akkor állhat el˝o, ha ez ablak átméretezhet˝o és azt a felhasználó nagyobbra nyújtja, mint amekkora hely a benne lév˝o widgetek kirajzolásához elégséges. Ezt az esetet szabályozzák, a konténer és a tárolt elem viszonyát meghatározó tulajdonságok (pl: fill, expand, packtype). Ezek a tulajdonságok voltaképpen nem a konténerhez, hanem a benne tárolt widgethez nem köt˝odnek, a widget viszonyát a konténerhez határozzák meg. Megadásuk akkor történik, amikor egy widgetet szeretnénk egy konténerben – például egy GtkBoxban – elhelyezni, tárolásukról pedig a konténer gondoskodik. GtkBox A boxokat, leginkább úgy képzelhetjük el, mint egy mindkét végén zárt dobozokat amikbe középr˝ol a két vége felé lehet pakolni. A hasonlat már csak azért is helytálló, mert az egyes elemek a dobozban történ˝o elhelyezése csak egymást követ˝oen, úgymond egymásra pakolva lehetséges. Annyiban viszont sántít a példa, hogy a GTK esetén rendkívül ritka – bár egyáltalában nem lehetetlen – hogy elemeket vegyünk ki egy konténerb˝ol. GtkGrid A grid egy rács – vagy ha úgy tetszik táblázatos formát – megvalósító konténer, aminek segítségével widgeteinket rács alakzatban sorokba és oszlopokba rendezhetjük. Az egyes oszlopok, illetve sorok azonos szélesség˝uek, illetve magasságúak, ennek révén alakul ki egy táblázatos forma, ahol az egyes ”cellákban” a táblázatba rakott widgetek helyezkednek el. Hasonlóan a boxokhoz az oszlopok és a sorok között megadható térköz (column-spacing, row-spacing), illetve megoldható, hogy az összes oszlop, illetve sor azonos szélesség˝u, illetve magasságú (column-homogeneous, row-homogeneous)legyen . A fentiekb˝ol talán nem következik els˝o látásra, de ez egy oszlopos, illetve egy soros GtkGrid tökéletesen megvalósítja a függ˝oleges, illetve a vízszintes elrendezés˝u GtkBox funkcionalitását. GtkOrientable A korábban említett konténertípusok mindegyik implementál egy, az orientációra vonatkozó interfészt, aminek révén lehet˝ové válik az orientáció flexibilis kezelése, akár futásid˝oben történ˝o megváltoztatása. Ennek el˝onyeit a mobilalkalmazások terjedésének fényében nem igazán kell részletezni.
7.2.
Alapmuveletek ˝
7.2.1.
Létrehozás
A GtkContainer, illetve a GtkBin absztrakt osztályok, így ebben a formájukban nem, csak a származtatott osztályok révén példányosíthatóak. A származtatott osztályok közül ebben a részben a legnépszer˝ubbet – a vízszintes vagy függ˝oleges elrendezést és megvalósító – GtkBox, illetve a rács, formát implementáló GtkGrid osztályt vesszük górcs˝o alá.
46
GtkBox Létrehozáskor a doboz elrendezését1 (orientation) túl még egy paramétert kell megadunk (spacing), ami az elemek között hagyandó térköz méretét adja meg pixelben. Létezik a két lehetséges orientációnak megfelel˝o saját típus is (GtkHBox, GtkVBox), amik létrehozáskor nem várják az orientációt, mint paraméter, viszont megadandó egy bool típusú paraméter (homogeneous), aminek TRUE értéke esetén minden egyes elem azonos helyet foglal majd el a konténerben, ahol a méret a értelemszer˝uen a legnagyobb helyigény˝u widget mérete lesz. Ezek a típusok újólag írt kódokban már nem javallottak, helyettük a GtkBox típus ajánlott, ami ugyanolyan egyszer˝uen használható, mint a két orientációnak megfelel˝oen specializált típus, ugyanakkor generikusabb megoldást kínál. A létrehozást követ˝oen természetesen ezen típus objektumainak is megadható a homogeneous tulajdonság. GtkGrid Létrehozáskor a GtkGrid a egyetlen paramétert sem vár. Míg a vízszintes elrendezés˝u boxoknál a widgetek szélessége, a függ˝olegeseknél a magassága, addig a táblázatoknál mindkett˝o azonos ha a homogeneous paraméter értéke TRUE.
7.2.2.
Elem hozzáadása
Ezen függvények közös sajátossága, hogy paraméterként átveszik azt a widgetet, melyet a konténerbe kívánunk helyezni. A korábban említett szül˝o-gyerek viszonyok által kialakított fa hierarchiából sajátosságaiból következik, hogy egy elem nem lehet több szül˝onek gyermeke – különben erd˝o szerkezetr˝ol beszélnénk fa hierarchia helyett –, azaz egy widgetet összesen egy konténerben helyezhetünk el. Ha esetleg ezt másodszor is megpróbálnánk – még miel˝ott a korábbi konténeréb˝ol eltávolítottuk volna – akkor futás idej˝u hibaüzenetet kapunk. Ne feledjük, a GTK rendelkezik referenciaszámlálási metódussal, azaz minden egyes objektum (GtkObject) – esetünkben GtkWidget – rendelkezik egy referenciaszámmal. Ha egy widgetet egy konténerbe helyezünk, annak referenciáját a konténer, annak rendje és módja szerint, növeli eggyel. Ez a referencia mindaddig megmarad, míg a widgetet el nem távolítjuk, vagy a konténer valamilyen oknál fogva meg nem sz˝unik, ami jellemz˝oen akkor következik be ha az egész ablakot megszüntetjük (destroy). Itt érdemes visszatérni a lebeg˝o referencia (floating reference) fogalmához. Egy widget referenciaszámlálójának értéke létrehozáskor egy. Ezt a konténer – amibe a widgetet helyezzük – nem növeli meg, csupán a lebeg˝o referenciát süllyeszti el, vagyis a következ˝o referenciát növel˝o m˝uvelet ténylegesen növelni fogja a referenciaszámláló értékét, növelés hiányában a következ˝o referencia csökkent˝o m˝uvelet – például a konténerb˝ol való eltávolítás – a widget megsemmisüléséhez (destroy) vezet. Ez hasznos abban a szokványos esetben ha egy adott widgettel együtt annak minden gyerekét is meg szeretnénk semmisíteni, ugyanakkor odafigyelést igényel abban a ritka esetben, amikor egy konténerb˝ol úgy szeretnénk eltávolítani egy elemet, hogy az ne semmisüljön meg. GtkContainer Az add nev˝u függvény egyetlen paramétert, az elhelyezni kívánt widgetet veszi át. Ritkán, leginkább csak egyszer˝u konténereknél alkalmazott hívás, lévén olyan alapértelmezett paraméterek használ a widget elhelyezésére, amik a felhasználó céljainak a legtöbb esetben nem felelnek meg. Használható ugyan a származtatott, bonyolultabb konténerek esetén is (pl: GtkBox, GtkGrid), de célszer˝ubb ezen esetekben az azokhoz tartozó, specifikus függvényt alkalmazni, lévén az sokkal rugalmasabban paraméterezhet˝oek. GtkBin Ebbe a típusba elemet csak a GtkContainer add függvényével tehetünk. Ha többször hívjuk meg a függvényt anélkül, hogy a korábban elhelyezett gyereket eltávolítottuk volna futási hibát kapunk, hiszen a GtkBin csak egyetlen elem tárolására képes. GtkBox Ahogy arról a bevezet˝oben szó volt a GtkBox típus olyan, mint egy két végén zárt doboz, amibe középr˝ol pakolhatunk két irányba. Ennek megfelel˝oen két olyan függvény van, amivel elemeket – akár többet is – helyezhetünk a boxba, az egyikkel az egyik iránya, elrendezést˝ol függ˝oen fel, illetve balra, a másikkal a másik irányba, elrendezést˝ol függ˝oen le, illetve jobbra. A pack_start felülr˝ol lefelé, illetve balról jobbra haladva tölti meg a konténert úgy, hogy az egymást után elhelyezett elemek egymás alatt, illetve balról-jobbra egymás mellett jelennek meg a boxba történ˝o behelyezés sorrendjében. A pack_end hívás ezekkel épp ellentétesen, alulról felfelé, illetve jobbról balra haladva helyez elemeket a tárolóba, szintén a hívás sorrendjében. Ahogy a GtkContainer esetén, itt is megadandó a boxba helyezend˝o widget, de ezen túl itt a konténeren belüli elhelyezkedést meghatározó értékek is. Az expand és fill bool típusú paraméterek, amik a korábban már említett 1 értsd
vízszintes vagy függ˝oleges orientációjú dobozról van-e szó
47
”felesleges” hely kitöltésére vonatkoznak. El˝obbi azt határozza meg, hogy a widget a konténeren belül rendelkezésre álló helyet kitöltse-e, vagyis ha egyáltalában van szabad hely, akkor azt igényelje-e magának (TRUE), vagy lemondjon róla (FALSE) a többi – a konténerben lév˝o – widget javára. Az expand paraméter annak beállítására szolgál, hogy a rendelkezésre álló – illetve az expand okán elnyert – helyre mi módon rajzolja ki magát a widget. Ha a paraméter értéke TRUE, akkor a widget maga tölti ki ezt a helyet, azaz a feltétlenül szükségesnél nagyobb helyen rajzolódik ki, míg ha az érték FALSE, akkor csak a minimálisan szükséges helyre rajzolódik és a maradék részt úgymond üresen hagyja. A pack függvények utolsó paramétere a padding nevet viseli, ami a widget körül (függ˝oleges elrendezés esetén felül és alul, függ˝oleges elrendezés esetén jobbról és balról) hagyandó üres hely értékét adja meg pixelben. Ha els˝ore nem is teljesen egyértelm˝u, mit jelent ez a gyakorlatban, a következ˝o fejezet illusztrációjából minden világossá válik. GtkGrid Az attach függvény – ami a táblázatok esetén elem elhelyezésére szolgál – bizonyos szempontból bonyolultabb, bizony szempontból egyszer˝ubb, mint a korábbi függvények. A táblázatnál természetesen vízszintes, illetve függ˝oleges pozíciót is meg kell adnunk, ahová a widget szánjuk, viszont sem fill, sem expand paramétereket nem kell megadni, lévén ezek a paraméterek widgetenként külön-külön nem állíthatóak. Másrészr˝ol lehet˝oség van arra, hogy egy adott widget több oszlopot illetve több sort is elfoglaljon. Ez esetben a left és right, illetve a top és a bottom paraméterek értékének különbsége nem egynél nagyobb.
7.2.3.
Elem eltávolítása
A származtatott típusok, legalábbis azok, amelyekkel ebben a részben foglalkozunk (GtkGrid, GtkBin, GtkBox) nem igényelnek az eltávolítás során semmilyen extra m˝uveletet, így a GtkContainer funkcionalitására támaszkodnak. GtkContainer A remove függvény értelemszer˝uen az eltávolítani kívánt widgetet várja paraméterként és ahogy azt említettük az általa tartott referenciát meg is szünteti. Ez egyben azt is jelenti egyben, hogy az eltávolított widgetre az utolsó – hisz többnyire csak a konténere tart referenciát egy widgetre – referencia és ezzel maga a widget is megsz˝unik. Ha ezt az esetet el akarjuk kerülni, akkor még az eltávolítás el˝ott a referenciaszám növelésér˝ol magunknak kell gondoskodnunk. Vagyis, ha két lépésben (remove és add) akarunk egy widgetet áthelyezni egyik konténerb˝ol a másikba, akkor az eltávolítás el˝ott növelnünk, a hozzáadás után pedig csökkentenünk kell a referenciát. Utóbbira azért van szükség, mert az új szül˝oelem maga is növel egyet a referencián, így ha mi, az általunk korábban megnövelt referenciát nem csökkentenénk, a widget soha nem sz˝unne meg. Hasonlóan a GtkBinhez itt sincs specifikus függvény az eltávolításra, hanem a GtkContainer remove függvényét hívjuk.
7.3.
Pa(c)kolás
7.3.1.
Elemek elhelyezkedése
Fogalmak Lássuk mire jó végül is ez a három opció (homogeneous, expand, fill) és mikét függenek össze egymással. Homogenitás. A konténer tulajdonsága, ami a gyerekek méretének egymáshoz való viszonyát határozza meg. Jelentése egyenl˝oség abban az értelemben, hogy minden egyes elem a konténerben pontosan ugyanakkora helyet foglal majd el. Ez a hely a természetesen a legnagyobb helyigénnyel rendelkez˝o widget mérete lesz, hiszen csak így biztosítható, hogy minden elem kiférjen, ugyanakkor azonos helyet foglaljon el. Terjeszkedés. A terjeszkedés (expand) azt határozza meg, hogy az adott widget megpróbál-e helyet szerezni magának a konténerben a többi widget rovására. Gyakorlatban ez annyit tesz, hogy a konténer által elfoglalt hely szabadon maradt része – vagyis amennyivel a konténer nagyobb, mint a benne lév˝o elemek minimális méretének összege –, ezen tulajdonsággal rendelkez˝o widgetek között kerül elosztásra. Kitöltés. A gyerek által elfoglalható terület kitöltésének (fill) módját leíró tulajdonság. Gyakorlatilag azt határozza megy, hogy az adott widget megrajzolásakor a rendelkezésére álló teljes helyet kihasználja a rendszer, függetlenül attól, hogy ehhez a mennyiség˝u helyhez miként jutott a widget (expand, homogeneous, . . . ), vagy csak a minimális szükséges helyet használja fel, míg a maradékot üresen hagyja. 48
Igazítás. A igazítás (align) azt határozza megy, hogy a konténerbe helyezett elem GtkBox esetén a konténeren belül, GtkGrid esetén a cellán belül merre igazodjék. Az igazítani lehet el˝ore (start), vagy hátra (end), ami természetesen függ˝oen az orientációtól – vízszintes, vagy függ˝oleges – bal, vagy jobb oldalt, illetve az alsó vagy a fels˝o részt jelenti. GtkBox A következ˝o ábra azt szemlélteti, hogy miként változtatja meg a homogenitás az expand, fill) tulajdonságok függvényében az elemek elhelyezkedését a konténeren belül.
7.1. ábra. Méretarányos és homogén elhelyezésének a konténeren belül[8]
Ahogy korábban a kódsorokat, most a widgetek sorait vesszük sorra a minél jobb megértés kedvéért. Méretarányos elhelyezkedés. expand = FALSE, fill = FALSE A konténerben lév˝o elemek – ahogy fentiekben fogalmaztunk – nem akarnak egymás rovására helyet szerezni (epxand), így a rendelkezésre álló vízszintes helyet nem is töltik ki, vagyis ezzel a megoldással az egész elemsorra nézve a szélekre történ˝o igazítást (pack_start esetén balra zárt, míg pack_end esetén jobbra zárt) alakítható ki. expand = TRUE, fill = FALSE Az összkép, leginkább az alatta található sor miatt kissé csalóka, mivel az elemek kissé rendezetlennek t˝unnek, ehelyett viszont arról van szó, hogy minden egyes elem megszerezte magának – a saját minimális méretigényének arányában – az expand paraméter okán rendelkezésre álló plusz helyet és az így allokált (size allocation) térrészen belül középen helyezkedik el. expand = TRUE, fill = TRUE Az egyes elemek nem csak hogy kiterjeszkednek (expand), hanem a korábban fel nem használt területre, de ki is töltik (fill) azt, azaz annak teljes méretében rajzolják is ki magukat. Ezzel a megoldással az elemsorra igaz, hogy az elemek között – a konténer spacing, illetve a konténerbe helyezéskori padding paraméter értékével meghatározottaktól eltekintve – térköz nincs. Ahogy az az ábrából – és talán a magyarázatból is – kit˝unik az fill opció állításának semmi teteje anélkül, hogy az expand be ne lenne kapcsolva, hisz e nélkül nincs semmilyen plusz terült, amire a widget magát megnagyobbítva rajzolhatná. Homogén elhelyezkedés. expand = TRUE, fill = FALSE A konténerben lév˝o elemek – a már használt kifejezéssel élve – hiába akarnak egymás rovására helyet szerezni (epxand) a konténerben, a konténer a rendelkezésre álló vízszintes helyet egyenl˝oen osztja el közöttük. Ezzel a megoldással az egész elemsorra igaz, hogy az elemek azonos helyet foglalnak el, de ezen helyen belül csak a minimálisan szükséges területre rajzolják ki magukat. expand = TRUE, fill = TRUE A fentiekhez képest az eltérés csupán annyi, hogy az egyes elemek ki is használják – ha úgy tetszik kitöltik (fill) az egyenl˝oen kiporciózott térrészt, maximális méretben rajzolva ki magukat. A különbség az el˝oz˝o sorhoz képes épp az, mint a nem homogén konténerek esetén ugyanazon paraméterezés esetén.
49
GtkGrid A GtkGrid típus szempontjából ugyanaz a két saját tulajdonság (homogeneous, spacing) számottev˝o, mint az imént GtkBox típus esetén amiket a fentiekhez hasonlóan befolyásol másik két paraméter. Ezek a paraméterek viszont ugyanúgy a konténer és a widget viszonyát határozzák meg, mint az expand és a fill és azokhoz hasonló módon is m˝uködnek, ugyanakkor a GtkWidget tulajdonságai, tehát ott is tárolódnak. Kiterjedés. Az expand önálló tulajdonságként is létezik és szerepe gyakorlatilag ugyanaz, mint a GtkBox típus esetén, ugyanakkor mégis van egy számottev˝o különbség. Az expand voltaképpen másik két tulajdonság (hexpand, vexpand) – amik a vízszintes és a függ˝oleges kiterjedés szabályozzák –, együttes kezelésére szolgál, amik külön-külön is létez˝o és értelmezett tulajdonságai a widgetnek. Igazítás. A fill gyerek-tulajdonság ebben a formában nem létezik, mint a GtkWidget típus tulajdonsága, viszont helyette két tágabban értelmezett tulajdonságot is használhatunk. Ez a tulajdonság az igazítás (align), amit szintén megadhatunk külön vízszintes és függ˝oleges (halign, valign) értelmezésben. A tulajdonság négy értéket vehet fel, amik közül egy a FILL, ami megegyezik a korábban részletezett fill gyerek-tulajdonság m˝uködésével. A mássága, illetve rugalmassága abban áll, hogy a másik három érték (START, END, CENTER) révén nem csak azt érhetjük el, hogy a widget a rendelkezésre álló, felesleges vagy éppen szándékoltan megszerzett (expand) helyet kitöltse, hanem azt is, hogy annak elején, végén, vagy éppen közepén helyezze el magát. A pozíció természetesen függ az orientációtól, így a START érték jelenthet bal oldalt, vagy éppen fels˝o pozíciót, ahogy az END jobb oldalt, vagy az üres térrész alját.
7.3.2.
Térköz, pányvázás és szegély
Fogalmak Térköz. A konténer tulajdonsága, ami az benne elhelyezett elemek egymástól vett távolságát, vagyis ez egyes elemek közötti térközt határozza meg. Fontos megjegyezni, hogy a konténer els˝o eleme el˝ott és utolsó elem után ez a térköz nem jelenik meg, csakis az elemek között. Ennek megfelel˝oen az egy elem˝u konténerek ezen tulajdonsággal nem is rendelkeznek. Pányvázás és szegély. Ha a konténerben az egyes elemek között nem egyenl˝o térközt szeretnénk megadni, akkor lehet˝oség van az egyes widgetek esetén külön-külön, csak az adott widgetre vonatkozó, a widgetet körülvev˝o térközt megadni. Ez a fajta térköz minden esetben megjelenik a widget megfelel˝o oldalán és a már említett térközhöz hasonlóan szintén függ az orientációtól. A GtkBox típus esetén csak egy – az orientációtól függ˝o irányban értelmezett – térköz megadására van lehet˝oség, amit pányvázásnak (padding) nevezünk és a widget mindkét oldalán megjelenik. A GtkGrid típus ett˝ol eltér˝oen lehet˝oséget ad az oldalankénti (top, bottom, left, right) szegélyérték (margin) megadásra külön-külön, de kezelhetjük az összes oldal egyben is. GtkBox
7.2. ábra. Tér az elemek között és körül a konténerben[8]
Tér az elemek között.
50
expand = TRUE, fill = FALSE Ez a példa nem mutatja meg igazán jól azt, hogy az elemek között jelenik meg az a térköz, amit a konténer létrehozásakor megadtunk, hiszen elemek nem töltik ki (fill) maximálisan a számukra rendelkezésre álló teret. expand = TRUE, fill = TRUE Mivel itt mindkét érték TRUE, a widgetek a rendelkezésre álló teret teljes egészében kihasználják maguk megrajzolására, eltekintve természetesen a közöttük megjelen˝o 10 pixelnyi térközt˝ol (spacing). Érdemes külön figyelmet fordítani a két széls˝o elemre, azoknak is az ablak széléhez közelebb es˝o részére a következ˝o megoldással való összehasonlításhoz. Tér az elemek körül. expand = TRUE, fill = FALSE A padding megadásával a térköz nem az elemek között, hanem azok körül jelenik meg. Ez azt jelenti, hogy minden elem jobb és bal oldalán (függ˝oleges elrendezés esetén felül és alul) egyaránt jelentkezik a megadott térköz, ennek okán közöttük annak – függ˝oen a fill értékét˝ol – a kétszerese. expand = TRUE, fill = TRUE Ez az az eset amikor igazán jól látható a widgetek között és az azok mellett megjelen˝o térköz 2:1 aránya. Az el˝obb – a széls˝o widgetek elhelyezkedésénél megfigyelteket – most hasznosíthatjuk, ha észrevesszük itt a széls˝o widgetek nem tudnak a konténer széléig kiterjeszkedni, lévén két oldalról ki vannak párnázva (padding) 10-10 pixellel. GtkGrid Az imént leírtak a GtkGrid, illetve a GtkWidget típus saját tulajdonságainak használatával annyiban változnak, hogy a pányvázás (padding) helyett, szegélyezést alkalmazhatunk, vagyis minden oldal esetében külön-külön is megadhatjuk a widgetet körülvev˝o térközt (margin-top, margin-bottom, margin-left, margin-right), amiket szintén kezelhetünk együtt (margin), ami íráskor az összes értéket felülírja, míg olvasáskor a legnagyobb értéket adja vissza.
7.4.
A kód
A fenti példaprogramok forrása a GTK+ oldalán érhet˝ok el.
7.4.1.
Fordítás és linkelés
A korábbiakhoz hasonlóan az alábbi parancssor segítségével fordítható a példaprogram: gcc gtk_packbox.c -o gtk_packbox ‘pkg-config --cflags --libs gtk+-3.0‘
7.4.2.
Futtatás
Próbáljuk ezúttal a ./gtk_packbox 1|2|3 paranccsal abban a könyvtárban, ahol a fordítást elkövettük, ahol a paraméter a teszt sorszáma, abban a sorrendben, ahogy azokat itt is ismertettük (a 3. természetesen csak ráadás).
7.4.3.
Eredmény
Ha netán úgy érezzük mégsem világos mi is történik, mikor és miért a konténerekbe pakolás kapcsán, ne adjuk fel. Els˝ore talán az egész mechanizmus jelent˝osége sem szembet˝un˝o, ugyanakkor érdemes próbálkozni, azaz venni a forrást és játszani a különböz˝o értékekkel (fill, expand, spacing, padding), illetve a létrehozott ablak átméretezésével.
7.5.
Tesztelés
Amint azt már megállapítottuk, a konténerek segítségével alakíthatjuk ki a felhasználói felület szerkezetét. Ezen eszközök teszik lehet˝ové, hogy a widgeteket egymásba ágyazzunk, ezek határozzák meg a widgetek konténerekben való elhelyezkedését, más widgetekhez való viszonyát. Az így létrejöv˝o szül˝o-gyerek viszonyok fa hierarchiát határoznak meg, aminek gyökerében maga az applikáció áll, második szintjén az applikáció egyes ablakai, ezek alatt pedig az ablakokon belüli widgetek, a felületi elrendezésnek megfelel˝oen. A konténerek által kialakított szerkezet természetesen a tesztelés során is tetten érhet˝o, bár nem szabad megfeledkeznünk arról, hogy a felhasználói felület tesztelésének alapjául szolgáló – a szoftverek akadálymentesítéséhez (accessibility) megalkotott – réteg némiképp másként tekint a widgetekre és a widgetek közötti összefüggésekre, mint azt a GTK teszi. Utóbbi természetesen er˝osen szoftverfejleszt˝oi szemszöget képviseli, míg az el˝obbi a felhasználói gondolatvilágból indul ki, a felhasználók által értelmezett fogalmakból, szerkezetb˝ol, összefüggésekb˝ol, tulajdonságokból építkezik, ebb˝ol adódnak azok az eltérések, amiket a kés˝obbikben folyamatosan számba veszünk.
51
7.5.1.
Gyerekek keresése
Az el˝oz˝o részben említett GenericPredicate nem csak sz˝urt keresések lebonyolítására alkalmas. Az alapértelmezett paraméterekkel létrehozott objektum használatának eredménye a gyakorlatban pont az, hogy az így létrejött elvárásoknak minden elem megfelel. Egy adott Node findChild függvénynek átadva az el˝obbi módszerrel létrehozott predicate objektumot, annak gyerekeit kaphatjuk vissza. Amennyiben a findChild függvény recursive paramétere True, akkor az összeg gyereket, amennyiben False, akkor csak a közvetlen gyerekeket. Azt már elöljáróban is érdemes megjegyezni, hogy a GTK widgetek által létrehozott hierarchia, illetve az accessibility eszközök által lekérdezhet˝o elemek hierarchiája nem egyezik meg tökéletesen. Bizonyos elemek esetén eltérések lehetnek, amiket azt adott helyen részletezünk.
52
8. fejezet
Megjelenít˝o eszközök sA szöveges adatbevitel legegyszer˝ubb módjairól ugyan volt szó az el˝oz˝o részben a kép azonban közel sem teljes, hiszen ablakok rendszerint nem csupán beviteli mez˝okb˝ol állnak, hiszen magukból a mez˝okb˝ol igencsak nehezen lehetne rájönni arra, hogy voltaképpen a mez˝okbe mit is kellene írnunk. Ebben a részben néhány nélkülözhetetlen, teljesen általánosan használt megjelenít˝o widgetet veszünk górcs˝o alá, úgy is mint a címkék, képek, súgó-, vagy leíróbuborékok.
8.1.
Fogalmak
8.1.1.
Igazítás és helykitöltés
A GTK+ definiál egy olyan o˝ sként szolgáló osztályt (GtkMisc), ami önmagában nem példányosítható, csupán a bel˝ole származó osztályok (GtkLabel, GtkImage, GtkArrow) közös beállításait fogja össze. Mindössze két tulajdonságról – igazítás (align), helykitöltés (padding) van szó tulajdonképpen, amik a widget tartalmának elhelyezkedését befolyásolják magán a widgeten belül, azaz függetlenül a widget konténerben elfoglalt helyét˝ol és annak paramétereit˝ol. Ez talán némiképp ködösen hangzik, de a származtatott osztályok ismeretében hamar világossá válik. Fontos már itt megjegyezni, hogy a GtkMisc némiképp idejétmúlt, szolgáltatásai egyszer˝usített – ugyanakkor a gyakorlati alkalmazás szempontjából mégis ugyanannyira kielégít˝o – formában a GtkWidget típus által is implementáltak, így ezen típus tulajdonságainak használata újólag írt kódokban ellenjavallt. Két oknál fogva mégis célszer˝u ezzel a típussal foglalkozni. Az egyik a kézenfekv˝o ok, hogy a GtkMisc még mindig része a GTK+ függvénykönyvtárnak és meglehet˝osen régen az, ennek okán pedig számos korábbi kódban találkozhatunk vele.
8.1.2. Widgetek Címkék Talán a legfontosabb és leggyakrabban használt kiegészít˝o widgetek a címkék, ahol értelemszer˝u az igazítás (alignment) jelentése,ami azonos azzal, amit a szövegszerkeszt˝o szoftverek esetén megszokhattunk. A helykitöltés (padding) m˝uködése a konténereknél már tárgyaltakkal egyezik meg.
8.1. ábra. Címke[9]
Képek A GTK – mint minden más felhasználó felület fejlesztéséhez használt eszközkészlet – lehet˝ové teszi képek megjelenítését a felhasználó felületek részeként. Az igazítás és a helykitöltés funkciója ebben az esetben is értelemszer˝u. Képek természetesen több forrásból is származhatnak. A felhasználói felületen természetesen megjeleníthetünk küls˝o forrásból származó képeket, ikonokat, animációkat, melyeket fájlból tölthetünk be, ugyanakkor a GTK maga is szállít számos olyan ikont, ami nehezen nélkülözhet˝o még a legegyszer˝ubb felületeken sem. Ilyenek például a leggyakrabban el˝oforduló gombok ( Ok, Mégsem, Alkalmaz, . . . ), az üzenetablakok ( hiba, figyelmeztet˝o, információs, . . . ) ikonjai.
53
8.2. ábra. Kép[9]
Beépített ikonok. Ezek a beépített (stock) ikonok a széles körben használt menüelemek, illetve eszközkészletek ikonjait jelentik, amikre azonosítók segítségével (id) hivatkozhatunk képek, gombok, dialógusok létrehozásánál. A beépített ionokat sajátjainkra lecserélhetjük, illetve saját stock ikonok regisztrálására is lehet˝oség van. Ikonhalmazok. Egy adott azonosítóhoz tartozó ikon méretbeli (menünek, gombnak, dialógusnak, . . . megfelel˝o méret), illetve a widgetek lehetséges állapotainak (normál, kiválasztott, aktív, . . . ) megfelel˝o variációk ikonhalmazokat hoznak létre, melyekben az egyes elemek a beépített ikonokhoz hasonlóan cserélhet˝oek. Buborékok Némiképp méltatlanul hanyagolt widget a súgó-, vagy más néven leíróbuborék (tooltip), pedig egy magára valamit is adó applikáció nem nélkülözheti ezt az eszközt, lévén ez a felhasználó informálásának egyik leginkább bevett módszere.
8.3. ábra. Buborék[7]
8.1.3.
Szövegformázás
A GTK, pontosabban szólva a bevezet˝o részben már említett Pango, rendelkezik egy saját leíró (markup) nyelvvel, a szövegek formázását teszi lehet˝ové a felhasználó felületen. Ezen nyelv segítségével állíthatjuk be a szöveg megváltoztatni kívánt paramétereit, úgy mint bet˝u típusa, mérete, stílusa, színe és így tovább. Ezen tulajdonságok leírását magával a szöveggel együtt adjuk meg. Blue textis cool!
A példában el˝oször a Blue text szöveg színét, illetve méretét állítjuk át igényeknek megfelel˝oen, úgy hogy a kívánt szöveg köré az egyes tulajdonságok (foreground, size), illetve azok értékeinek leírását helyezzük el. Ezzel a módszerrel ez egyes tulajdonságokat külön-külön adhatjuk meg, ugyanakkor a gyakran, és jellemz˝oen egymagukban használt beállításokhoz (félkövér, d˝olt, aláhúzott) léteznek önálló leírók is (b, i, u), ahogy ez a cool szövegrésznél is látszik.
8.1.4. Widgetek összefüggései Bizonyos típusú widgetek sajátja, hogy nem önmagukban léteznek, hanem vagy valamilyen csoportnak tagjai, vagy egy konkrét widgettel állnak valamilyen összefüggésben. Ez utóbbi igaz a címkék esetén is, amiknél megadható, hogy melyik másik widget az, amire vonatkoznak, amit leírnak. Ez az összefüggés egyes widgettípusok esetén (pl: gombok) esetén automatikus, míg más esetekben (pl: beviteli mez˝ok, listák, . . . ) magunknak kell megadnunk. Ezen kapcsolat megadásának közvetlen el˝onye egyrészr˝ol a tesztelés során mutatkozik meg, ahol a címke segít a leírt widget megtalálásában, másrészr˝ol a felhasználó felület billenty˝uzetr˝ol történ˝o használatát könnyíti meg, ahogy arról a kés˝obbiekben szó esik.
8.2.
Alapmuveletek ˝
8.2.1.
Létrehozás
Az ebben a fejezetben tárgyalt widgetek esetén – hasonlóan a korábbiakhoz – a létrehozás maga nem különösebben bonyolult feladat. Némi rutint csak a létrehozást követ˝o testreszabás igényel, amihez szükséges az alapvet˝o m˝uködési 54
sajátosságok tisztázása. A kezdeti beállításokat követ˝oen jellemz˝oen ezen widgetek nemigen változnak, úgyhogy ezt követ˝oen már csak a widget konténerbe való behelyezése jelenthet kihívást. GtkLabel A legegyszer˝ubb eset, ha szeretnénk valamilyen statikus szöveget, mindenféle formázás nélkül megjeleníteni a felhasználói felületen. Erre a problémára a megoldás is rendkívül egyszer˝u. Mindhárom nyelvi változatban esetén csupán egyetlen paramétert kell megadnunk a címkét létrehozó függvénynek, ez pedig a címke szövege. GtkWidget * gtk_label_new ( constgchar *label ); GtkWidget * gtk_label_new_with_mnemonic ( constgchar *label );
explicit Label ( constGlib::ustring& label, boolmnemonic = false );
def__init__(self, label=None, **kwds):
Label ( constGlib::ustring& label, floatxalign, floatyalign, boolmnemonic = false ); Label ( constGlib::ustring& label, Alignxalign, Alignyalign = ALIGN_CENTER, boolmnemonic = false );
Kódrészlet 8.1. Címke létrehozása Ahogy látszik a különböz˝o nyelvi változatoknál a címkék már létrehozáskor ennél változatosabban paraméterezhet˝o. A létrehozás követ˝oen pedig további nyilván paraméterek is állíthatóak. Kés˝obbiekben meglátjuk mik ezek a paraméterek és milyen haszonnal bírnak a hétköznapi használat során, most vegyük sorra a létrehozás paramétereit. label A címke szövege. Általánosságban véve a címkék szövegeinek megalkotásánál célszer˝u valamilyen egységes irányelvet követni. A GNOME által követett irányelvek szerint a GtkLabel típus felhasználási területein – legyen az a beviteli mez˝ok címkéi, jelöl˝onégyzetek szövegei, vagy más egyéb – a mondatok els˝o szava írandó nagybet˝uvel, míg a többi kisbet˝us. mnemonic A címkék leggyakoribb felhasználása a beviteli mez˝ok illetve jelöl˝onégyzetek címkézése. Ezen esetekben a címkék szövegében megjelölhetünk egy karakter úgy, hogy egy aláhúzást karakter írunk elé a szövegben (pl: "_Label text"), és azt a karaktert kés˝obb a címke által hivatkozott widget elérésére használhatjuk. A gyakorlatban ez a billenty˝uzetr˝ol történ˝o navigációt – ezzel együtt a felhasználói felület hatékonyabb használatát – segíti el˝o azáltal, hogy a widget, az el˝obb említett példánál maradva, az Alt+L billenty˝uvel aktiválható lesz. A címke által hivatkozott widget alapértelmezés szerint az a widget – pontosabban az a konténer – lesz amibe a címkét helyeztük, vagy annak els˝o olyan szül˝oje, ami implementálja a GtkActivatable interfészt. Az olyan widgetek, amik maguk is tartalmaznak címkét (pl: gombok, jelöl˝onégyzetek, . . . ) ez a feltétel kézenfekv˝oen adott, mivel a widget implementálja a GtkActivatable interfészt. Amennyiben nem ez a helyzet, hanem például egy beviteli mez˝ot címkézünk, akkor magunknak kell megadnunk a kapcsolódó widgetet a set_mnemonic_widget függvény segítségével, aminek értelemszer˝uen az aktiválandó widget a paramétere. Az aktiváláskor a beállított widget az aktiválás hatására fókuszba kerül, ami egy beviteli mez˝onél például azzal az el˝onnyel jár, hogy a nem kell váltogatnunk az egér és a billenty˝uzet között a felület használatakor. xalign, yalign A vízszintes, illetve függ˝oleges igazítás 0 és 1 közötti lebeg˝opontos értékekkel adhatóak meg, ahol a 0 a bal oldalt, illetve a fels˝o pozíciót, az 1 pedig a jobb oldalt, illetve az alsó pozíciót jelenti. Mind a vízszintes, mind a függ˝oleges igazítás alapértelmezett értéke 0,5. Ez az esetek jelentékeny részében nem felel meg az igényeknek, hiszen a címkéket többnyire balra, esetenként jobbra igazítjuk, vagyis az xalign tulajdonságot értéke 0, illetve 1 kell legyen. Bár a GtkMisc osztály, illetve annak tulajdonságai még érvényben vannak, a GTK fejleszt˝oi nem ajánlják használatukat újólag írt kódban, lévén a GtkWidget típus korábban már említett halign, valamint valign tulajdonságai helyettesítik o˝ ket. xpad, ypad Az itt megadott értéknek megfelel˝oen a vízszintesen, illetve függ˝olegesen ad térközt a widget köré. Hasonlóan azonban az el˝obbiekhez ennek a módszernek a használata sem javasolt új kódokban, helyette a GtkWidget margin tulajdonsága állítandó. GtkImage Ahogy arról szó esett a bevezet˝oben, képek létrehozására számos mód kínálkozik. Ezek közül most csak a népszer˝ubbeket vesszük számba. Az els˝o és talán legfontosabb a fájlból való betöltés, amire a new_from_file függvényt használhatjuk.
55
Amennyiben a megadott fájl betöltése valamilyen oknál fogva sikertelen (pl: fájl nem létezik, jogosultsági problémák, . . . ), akkor egy olyan képet kapunk vissza, ami a betöltési hibára utal. Amennyiben a fájl betöltése során felmerül˝o hibákat magunk szeretnénk kezelni egy alacsonyabb szint˝u megoldást kell választanunk, ami egyébiránt a GTK fájlból való betöltés végz˝o függvény hátterében is áll. Ezt a megoldás nem meglep˝o módon a GDK adja, hiszen a kifejezetten grafikai kódok itt kapnak helyet. A GdkPixbuf típus szintén rendelkezik fájlból való betöltésre alkalmas függvénnyel (new_from_file), ami a GtkImage hasonló függvénnyel szemben hiba esetén a nyelvi változatnak megfelel˝o hibajelzés történik. A C nyelv˝u változat esetén a hibát egy GError típusú változóban kaphatjuk vissza, míg a C++, illetve Python változat esetén kivél váltódik ki. A harmadik eset amikor egy beépített ikont szeretnénk képként használni, amit megtehetünk a new_from_stock függvény használva, ami paraméterként a beépített ikon (stock icon) nevét veszi át paraméterként, éppúgy, ahogy teszi azt a gomb létrehozáskor. GtkTooltip A súgóbuborék funkciója és megadása is hasonlít némiképp címkééhez, hiszen mindkét widget egy másik widget azonosítására, szerepének tisztázására szolgál. Mindkét widget valamilyen szöveges leírást ad hozzá tartozó felületi elemr˝ol, a címke rövidebb, míg a súgóbuborék rendszerint hosszabb formában. Ennek okán a súgóbuborék megadásának legegyszer˝ubb módja azonos a címke létrehozásánál leírtakkal, vagyis csupán a kívánt szöveget kell megadnunk paraméterként. Mivel a súgóbuborék csak konkrét widgethez tartozhat, a függvény a GtkWidget típus függvénye (set_tooltip_text).
8.2.2.
Megjelenítés
GtkLabel Formázás. A címkék szövegének formázására a bevezet˝oben említett Pango Markup Language elnevezés˝u leírónyelv használható. A címkék létrehozása úgy történik, hogy a megadott szöveget alapértelmezetten nem tekintjük leíró nyelven megfogalmazottnak, azaz a use-markup tulajdonság értéke FALSE. Ez azonban a névkonvenciónak megfelel˝oen a set_use_markup függvénnyel megváltoztatható. Ezzel azonban kell˝o óvatossággal kell bánnunk. Amennyiben a use-markup tulajdonság értéke TRUE, a címke szövege meg kell feleljen leíró nyelv szabályainak. Ez két esetben kritikus. Az egyik, ha a szöveg olyan elemeket tartalmaz, amik a leíró nyelvben is értelmezettek. Ebben az esetben az ilyen elemeket úgy kell megváltoztatnunk (escape), hogy leíró nyelvnek megfeleljen, ugyanakkor a jelentése ne változzon. A Glib markup_escape_text függvénye pontosan a leírtakat implementálja. Ezen függvényt minden olyan esetben használandó, amikor nem statikus szövegr˝ol van szó, hanem a szöveg például felhasználótól származik. Ilyen lehet például egy korábban bekért név, ami tartalmazhat például kisebb, vagy nagyobb jelet. A másik kritikusnak mondható eset, ha a leíró nyelv˝u szöveget egy printf típusú formátumleíróval akarjuk létrehozni. Ehhez a szintén a Glib részeként elérhet˝o markup_printf_escaped függvény nyújt segítséget. Tördelés. Hosszabb szöveg˝u címkék használata esetén – ami jellemz˝oen akkor fordul el˝o, ha egy magyarázó szöveget akarunk például egy üzenetablakban megjeleníteni – célszer˝u tördelnünk a szöveget elkerülend˝o, hogy a szöveg helyigénye miatt a címke – és ezzel együtt az üzenetablak – vízszintes helyigénye aránytalanul megn˝ojön, ami esetleg ahhoz vezethet, hogy az ablak kilóg a munkaterületr˝ol, így annak egyes funkció – legrosszabb esetben a bezáró gomb – elérhetetlenné váljanak. Erre természetesen több módszer is kínálkozik. Magunk is megadhatunk a szövegben tördelést azáltal, hogy sortörést teszünk a szövegbe. Ez egyszer˝ubb estekben megteszi, de nem túl elegáns megoldás, mivel nem számol a megjelenítend˝o címke számára aktuálisan rendelkezésre álló hellyel, vagyis ha a címkét tartalmazó ablak méretét növeljük, a sortörés helye nem változik, a szöveg nem használja ki a rendelkezésre álló helyet, csökkenteni pedig csak addig lehet az ablak méretét, amíg el nem érjük a leghosszabb sor minimális helyigényét. A probléma akkor igazán szembet˝un˝o, ha címke szövege nem statikus, hanem valamilyen felhasználótól származó adat (pl: objektum neve, IP cím, . . . ), vagy valamilyen módszerrel sz˝ur lista (pl: hibás elemek listája) szerepel benne. Célravezet˝o a GTK, konkrétabban a Pango által nyújtott kész megoldás használata. Ez lehet˝ové teszi a címke különböz˝o módokon történ˝o automatikus tördelését. Ehhez el˝oször is engedélyeznünk kell ezt a funkciót (set_line_wrap), másrészr˝ol választanunk kell tördelési módot. Ez utóbbi alapértelmezetten a szóhatárokon való tördelés (PANGO_WORD), ami helyett a set_line_wrap_mode függvény meghívásával beállíthatunk karakterenkénti (PANGO_WORD), illetve vegyes (PANGO_WORD_CHAR), ami azt jelenti, hogy egy szó kifér a rendelkezésre álló helyen a sor végén, akkor szóhatáron történik meg a sortörés, ha nem, akkor az adott szóból annyi karaktert teszünk az adott sorba amennyi még oda kifér. Részben megmutatás. Ha a rendelkezésre álló hely csekély és a címke valamely részletéb˝ol (eleje, vége, esetleg mindkett˝o együtt) következtethetünk a címke teljes szövegére1 , akkor elegend˝o lehet csak a ténylegesen információt hordozó részt megmutatnunk. A feleslegesnek ítélt részeket pedig úgymond kihagyhatjuk. 1 bizonyos
elnevezési konvenciók esetén a nevek els˝o része mindig azonos, vagyis ez nem hordoz érdemben információt, így helysz˝uke esetén érdemes csak a szövegek végét megmutatni
56
Ez a kihagyás a gyakorlatban úgy történik, hogy megadjuk, melyik az a része a címkének (eleje, közepe, vagy vége) amit elhagyhatónak ítélünk és hely sz˝ukében a tényleges szöveg helyett csak a kihagyás jelz˝o karakter (ellipsis: ’. . . ’) írunk ki. A beállításra szolgáló függvény értelemszer˝uen a set_ellipsize, paramétere pedig az elhanyagolás pozíciója, ami lehet a címke eleje (PANGO_ELLIPSIZE_START), közepe (PANGO_ELLIPSIZE_MIDDLE), vége (PANGO_ELLIPSIZE_END), illetve módunk van kikapcsolni ezt a funkciót (PANGO_ELLIPSIZE_NONE). Szélesség. Az el˝oz˝o két funkció használatakor két tulajdonság révén kontrollálhatjuk a címke szövegének szélességét. Az egyik (width-chars) a szöveg kívánt szélességét adja meg karakterekben, míg a másik (max-width-cars) révén a maximális szélességet határozhatjuk meg szintén karakterekben. Mindkét tulajdonság esetén megadható -1, mint szélesség, ami automatikus szélességkalkulációt jelent, ami érthet˝o okoknál fogva az alapértelmezett érték.
8.2.3.
Kezelés
GtkLabel Kijelölés. Alapértelmezetten a címkék nem kijelölhet˝oek és ez a m˝uködés a legtöbb esetben meg is felel a kívánalmaknak. Vannak azonban olyan esetek, ahol kifejezetten zavaró, ha a szövegeket nem lehet kijelölni és ezzel együtt másolni sem. Ilyen eset például, amikor hibaüzeneteket jelenítünk meg címkék segítségével. Az ehhez hasonló esetekben a felhasználó számára roppant bosszantó, hogy bár a hibaüzenet látható, mégsem lehet az egyszer˝uen átmásolni például egy hibabejelent˝o u˝ rlapra. Ennek elkerülésére a címkét kijelölhet˝ové tehetjük (set_selectable, ám célszer˝u ezt csak akkor megtenni, ha a címke fontos és manuálisan nehézkesen reprodukálható információt tartalmaz, mivel a kijelölhet˝oség egyben fókuszálhatóságot is jelent, ami billenty˝uzetr˝ol való kezelést megnehezíti.
8.3.
Haladó muveletek ˝
8.3.1. GtkLabel Hivatkozás. A címkék formázásának egy speciális esete, amikor hivatkozást szeretnénk a szövegben elhelyezni. A HTML esetén használatos módszert alkalmazhatjuk a Pango leíró nyelv esetén is, vagyis a hivatkozás szövege nyitó-, illetve záróelem között helyezkedik el, míg a hivatkozás a href attribútum értékeként adható meg. A hivatkozások épp úgy viselkednek, mint ahogy azt a böngész˝ok esetén megszoktuk, vagyis a hivatkozás szövege aláhúzott, színe pedig megváltozik a hivatkozás els˝o aktiválása után. A hivatkozás aktiválásának eseményét magunk is kezelhetjük, a activate-link szignál segítségével. A kezel˝ofüggvényben a kívánt m˝uveletsor hajtható végre, annak függvényében, hogy a címke melyik hivatkozása került aktiválásra, amit az értéket a kezel˝ofüggvény paramétereként is megkapunk. A függvény visszatérési értéke – hasonlóan a delete-event szignálhoz – azt fejezi, ki, hogy az eseményt kezeltük-e. Ezért az ott leírtak ennek az szignálnak a kezelésénél is alkalmazhatóak.
8.3.2. GtkTooltip Testre szabott súgóablak. Formázás. A korábban leírt formázó nyelv a súgóablakok szövegében is alkalmazható. A GtkWidget osztály set_toolip_markup függvénye paraméterként ilyen leírónyelv˝u szöveget vesz át paraméterként és annak megfelel˝oen formázza a súgóablakban megjelen˝o szöveget. Saját ablak. Amennyiben ennél is tovább szeretnénk menni – esetleg képeket helyeznénk el a súgóablakban – akkor saját súgóablakra van szükségünk. Ezt a saját ablakot a korábban már ismertetett módszerekkel hozhatjuk létre és abban gyakorlatilag bármit elhelyezhetünk. A megjelenítésr˝ol, eltüntetésr˝ol, illetve ezek id˝ozítésér˝ol továbbra is a GTK gondoskodik, nekünk csak a tartalmat kell biztosítanunk. Az elkészült ablak beállítható a GtkWidget osztály set_tooltip_window nev˝u függvényével, aminek a beállítandó ablak a paramétere, vagy a nyelvnek megfelel˝o NULL érték, ami az alapértelmezett ablak visszaállítását jelenti. Amennyiben a szokásos sárga súgóablak témát szeretnénk viszontlátni, az ablak nevét gtk-tooltip értékre kell állítanunk, amit könnyedén megtehetünk a GtkWidget osztály set_name függvényével.
57
8.4.
Tesztelés
8.4.1.
Objektum
A Dogtail Node típusa által reprezentált objektum az amin keresztül gyakorlatilag minden információ elérésére lehet˝oségünk van, amire a tesztelés során szükségünk lehet. Számos esetben közvetlenül az objektum függvényének meghívása, vagy attribútum kiolvasása révén, más esetekben valamilyen interfészen keresztül. El˝obbiekb˝ol néhányat veszünk a továbbikban sorra. Keresés. Az ebben a részben tárgyalt widgettípusok közül a GtkTooltip a legegyszer˝ubb és egyszersmind a leggyakoribb esetben nem valódi típus, mint ahogy azt a fejlesztésr˝ol szóló részben tárgyaltuk, ennek okán a tesztelés során sem jelenik meg külön típusként, kiolvasására Node objektumból közvetlenül van lehet˝oség. A GtkLabel és a GtkImage típusú objektumok az akadálymentesítés szempontjából betöltött szerepének neve (roleName) label, illetve icon. Az ilyen típusú elemek keresésénél tehát ezeket az értékeket kell megadni a roleName paraméternek. A másik keresési lehet˝oség, hogy az objektum nevére keresünk. A címke esetén az objektum Súgószöveg. Az egyes widgethez tartozó súgóbuborék szövegét rendkívül egyszer˝uen megállapíthatjuk, ez a textttNode objektum description attribútumának értéke.
8.4.2.
Állapotok
GtkLabel Többsoros szöveg. A címkék alapvet˝oen alkalmasak többsoros megjelenítésre, így egy állapot ezen widgetek kapcsán bizonyos lesz mégpedig a MULTI_LINE, amit a korábbiakban ismertetetteknek megfelel˝oen a getState függvénnyel tudunk lekérdezni. Létezik egy másik – ezzel ellentétes értelm˝u – státusz is, ami a címkék esetén természetesen sosem lesz része az állapothalmaznak, ez pedig a SIGNLE_LINE. Fókuszálhatóság. A címkék kapcsán fontos lehet, hogy a szöveget ki lehessen jelölni, ami egyúttal magával vonja, hogy az adott widget fókuszálhatóvá is válik, ami alapértelmezés szerint nem igaz. Ezt a m˝uködést tehát ellen˝orizni is tudjuk a megfelel˝o állapoton (FOCUSABLE) meglétén keresztül.
8.4.3.
Interfészek
Csak olvasható szöveg A csak olvasható szövegek kezelésére az ATK egy külön interfészt definiált (AtkText). Az olyan elemekhez, amik az akadálymentesítés és éppúgy a tesztelés szempontjából is csak olvasni szokás, az ATK megvalósító implementáció csak olvasási hozzáférést ad, annak ellenére is, hogy természetesen megvalósítható lenne, az írást biztosító implementáció is. Ilyen elemek például a címkék, a táblázatos megjelenítést biztosító widgetek egyes cellái, illetve minden olyan elem, ami írható hozzáférést is ad, mint például egy egysoros beviteli mez˝o. Az interfészen keresztül nem csupán a szöveget magát, de annak egyes részeit, illetve az egyes részek formázási paramétereit is elérhetjük. A queryText függvénnyel lekérdezhet˝o interfész objektum számos szolgáltatást nyújt, bár ezek jelentékeny részére – mint amilyen például a szövegrészek kezelése – a címkék során nem, vagy csak nagyon ritkán lesz szükségünk, így ezekkel majd egy kés˝obbi részben foglalkozunk. A legfontosabb információ természetesen maga a szöveg, esetenként annak hossza, el˝obbi a getText függvényen, utóbbi a characterCount attribútum révén érhet˝o el. A getText függvény esetén fontos körülmény, hogy az két kötelez˝o paraméterrel is rendelkezik, a kíván szöveg kezdeti- és végpozíciójának indexével, ahol a második paraméter esetén a −1 érték a szöveg végét jelenti. Mivel teljes szöveg a leggyakrabban érdekl˝odésre számot tartó érték, a Dogtail ehhez a text interfészt, illetve annak m˝uködését elrejt˝o elérést is biztosít a Node osztály text tagja révén, aminek kiolvasása a queryText().getText(0, -1) szerinti hívásával közel egyenérték˝u. A különbség az, hogy text attribútum kiolvasása kezeli azt a helyzetet, hogy az adott objektum nem implementálja a text interfészt, ilyen esetben None értékkel tér vissza, míg ilyen esetekben a queryText függvényhívás – minden más interfész lekérdezéséhez hasonlóan – NotImplementedError kivételt dob. A szöveg, amit a text interfészen keresztül kiolvashatunk csak a nyers szöveget tartalmazza aze egyszer˝u kezelés érdekében annak formázási paramétereit nem. Ha azt szeretnénk megtudni, hogy az egyes karakterek miként vannak formázva, akkor a text interfész getAttributeRun függvényét használhatjuk, aminek egyetlen paraméter a karakter sorszáma, visszatérési értéke pedig egy lista, ami els˝o eleme egy újabb lista, ami a karakter formázási paramétereinek tartalmazza, másik két eleme pedig annak szövegrésznek a kezd˝o és végpozíciója, ami ugyanezen paraméterekkel lett formázva.
58
Kép A képek kezelésére létezik egy külön interfész az ATK függvénykönyvtárban, amihez tartozó implementációt a Dogatil egy Node objektumára a queryImage függvénnyel kérdezhetünk le. Az interfész sajnos nem nyújt különösebben széles kör˝u szolgáltatásokat, mindössze a kép méretét, elhelyezkedését és leírását áll módunkban lekérdezni. Sajnálatosan a Dogatil ehhez nem sok segítsége nyújt, ezért is kell az Image interfészt elkérnünk és azt magunknak kezelnünk. Ez az interfész viszont már átvisz minket a Dogtail alatt meghúzó AT SPI 2 világába, aminek részleteiben nem célunk elmerülni, így itt csak azokat a hívásokat ismertetjük, amikre a képek tesztelésénél szükségünk lehet. Az el˝obb említett paraméterek közül a legegyszer˝ubb a kép mérete, amit a getImageDescription paraméter nélküli függvény hívása révén kérdezhetünk le, ami az x, illetve y irányú pixelben vett méretek listáját adja vissza. A pozíció lekérdezése is teljesen hasonlóan m˝uködik, méret helyett az x, y koordinátákat visszakapva a listában, viszont a pozíció maga relatív és hogy mire relatív azt a getImagePosition függvénynek paraméterként kell átadnunk. Ez a paraméter lehet a pyatspi modul WINDOW_COORDS, vagy DESKTOP_COORDS értéke, aminek megfelel˝oen vagy az ablakhoz, vagy magához a munkaterülethez képest értelmezend˝oek a koordináták. Lehet˝oség vagy a két értékpár együttes lekérdezésére is a getImageExtents függvénnyel, ami ugyanazt a paraméter veszi át, mint a getImagePosition és szintén egy listával tér vissza, aminek els˝o két eleme a relatív koordinátákat, második két elem pedig a méreteket tartalmazza. Ezen értékekb˝ol messzemen˝o következtetéseket levonni nem lehet. Különösen akkor vagyunk bajban ha két azonos méret˝u ikon között szeretnénk különbséget tennünk a tesztelés során, ami nem ritka példa, hiszen szokás különböz˝o szín˝u ikonokkal mondjuk valamilyen állapotot jelezni. Ebben az esetben mentsvárunk a kép leírása lehet, amit a Node imageDescription értéke tartalmaz és amit alkalmazásunk fejlesztésekor az AtkObject set_image_description függvénnyel állíthatunk be.
8.4.4.
Viszonyok
Az egyes widgetek között bizonyos vonatkozásokban összefüggések állhatnak fenn. Ezeket az összefüggéseket – amiket szabad fordításban nevezhetünk viszonyoknak (relation) –, a Dogtail segítségével is le tudjuk kérdezni. Túlnyomó többségben nem is viszonyokról, hanem viszonypárokról beszélünk, amik között 1 : 1 és 1 : N típusú kapcsolatok egyaránt vannak. Általánosságban az mondható el, hogy minden egyes Accessible objektum többféle viszonyban is lehet, több más objektummal. Els˝o körben tehát azt tudhatjuk meg, hogy mik azok a viszonyok, amikkel összekapcsolják az adott objektumot más objektumokkal. Ennek lekérdezésére a getRelationSet függvény szolgál, ami egy konténert ad vissza, benne a viszonyok leírására szolgáló Atspi.Relation objektumokkal. Ezen objektumokból kideríthet˝o a viszony típusa a getRelationType függvény hívásával, valamint, hogy a hány objektummal áll fenn az adott viszony, amit a getNTargets függvény visszatérési értéke ad meg. Az N darab objektum egyesével a getTarget függvény révén érhet˝o el paraméterként egy sorszámot átadva. Az egyik viszony, a címkére és az általa felcímkézett widgetre vonatkozik, ahol természetesen több címke is vonatkozhat ugyanarra widgetre, viszont egy címke egyszerre csak egy widgetet vonatkozhat. Ez következik a GtkLabel osztály set_mnemonic_widget függvény használatából is, hiszen paraméterként csak egy widget adható meg. A viszony típusa ebben az irányban pyatspi.RELATION_LABEL_FOR, ahol tehát a getNTargets függvény mindig egyet ad vissza, míg az ellenkez˝o irányban a viszony típusa pyatspi.RELATION_LABELLED_BY, ahol több cél is lehetséges, de ez nem jellemz˝o. Erre a gyakran használt viszonypárra a Node osztály is ad megoldást, a relation interfész közvetlen használatánál számottev˝oen egyszer˝ubbet. Egy adott Node címkéinek listája a labeller, az adott Node által címkézett objektum pedig a labelee attribútum keresztül érhet˝o el.
2a
rövidítés az angol Assistive Technology Service Provider Interface kifejezést takarja
59
9. fejezet
Egysoros beviteli mez˝ok Most, hogy már túl vagyunk a konténerek mibenlétének megtárgyalásán, ideje azzal foglalkozni milyen widgeteket lehet a konténerekben elhelyezni. Az els˝o és talán legkézenfekv˝obb megoldás erre a kérdésre egy beviteli mez˝o, hiszen ablakaink jelentékeny részét arra használjuk, hogy a felhasználótól adatokat kérjünk be. Ez a rész a beviteli mez˝okkel általános és az egysoros beviteli mez˝ok konkrét fejlesztési és tesztelési kérdéseivel foglalkozik.
9.1.
Fogalmak
9.1.1.
Beviteli mez˝ok típusai
Az adatbevitelre szolgáló widgetek számos szempont szerint csoportosíthatóak. Ezek közül csak a legkézenfekv˝obbeket vesszük röviden sorra, azokat addig a mértékig, amíg ezen rész szempontjából érdekesek. Bevitt adat típusa szerint Gyakorlatilag minden adat bevihet˝o szövegként, ugyanakkor nem mindig ez a leginkább célravezet˝o módszer. Felhasználói szempontból egy üresen álló szöveges beviteli mez˝o meglehet˝osen kétségbeejt˝o látvány. Ilyen esetben ugyanis semmi nem utal arra, hogy voltaképpen a beviteli mez˝ot milyen típusú, formátumú, hosszúságú, kódolású szöveggel lehet feltölteni. Emiatt mindig célszer˝u a lehet˝o legspecifikusabb widgetet alkalmazni. Emellett persze az is igaz, hogy a leggyakoribb beviteli eszközünk mégiscsak a szöveges beviteli mez˝o marad.
(a) Szövegbeviteli mez˝o[9]
(b) Számbeviteli mez˝o[9]
9.1. ábra. Szöveg és szám bevitele
Szöveg. A szöveg bevitele tehát a legáltalánosabb szükséglet, amit egy UI kapcsán elképzelni lehet, mégis számos olyan funkció létezik, ami egy szöveges bevitelt lehet˝ové tev˝o widgetnek teljesítenie kell. Ilyen például kijelölés, a másolás, a beillesztés, beszúrás, a unicode karakterek kezelése, a használt bet˝utípus-paraméterek megadásának lehet˝osége akár karakterenként. Erre a GTK szöveges bevitelre alkalmas widgetei természetesen mind képesek, s˝ot, de err˝ol majd kés˝obb. Szám. Számok bevitele gyakorlatilag a szövegbevitel egyfajta specializációja, legalábbis a bevitel ellen˝orzésének tekintetében. Számok bevitele esetén nyilván csak számjegyeket, illetve a számok beviteléhez kapcsolódó egyéb karaktereket (tizedes vessz˝o, el˝ojel, . . . ) engedünk bevinni. Emellett egy widget nyújthat még egyéb kényelmi szolgáltatásokat is amik megkönnyítik a fejleszt˝ok munkáját, min például a minimális, maximális elfogadható érték, tizedes jegyek számának meghatározása, lépésköz megadása az érték lépésenkénti megváltoztatásához, vagy akár számként való beállítás, illetve lekérdezés lehet˝osége. Sorok száma szerint A sorok száma szerint mindössze két típus megkülönböztetésének van létjogosultsága. Az egy, illetve a több sor kezelésére alkalmas widgetek mind a felhasználás körében, mind a kezelés módjában, mind pedig a tesztelésben gyökeresen eltérnek 60
egymástól. Egysoros beviteli mez˝ok. Ezen beviteli mez˝ok esetén a tárolt, illetve megjelenített értéket jellemz˝oen egy egységként kezeljük. Egyszer˝u, néhány tíz karakternyi, adatot kérünk be ezen widgetek segítségével, így sem különösebb szövegszerkesztési, sem pedig látványos megjelenítési funkciókat nem kell a widgetnek ellátnia. Ennek megfelel˝oen sem a használat, sem pedig a tesztelés nem jelent alapesetben különös kihívást. Többsoros beviteli mez˝ok. Lévén ezek a widgetek gyakorlatilag egy egyszer˝ubb szövegszerkeszt˝o alkalmazásként is felfoghatóak, akár komplett fájltartalmak kezelésére is alkalmasak, akár olyan extra funkciók igénybevétele mellett, mint a tartalom formátumának megfelel˝o szintaxis kiemelés (syntax highlight). Ennek megfelel˝oen a kezelés sem annyira kézenfekv˝o, mint az egysoros widget esetén. Tesztelési szempontból azonban – mindaddig amíg csak a tartalmat egyben akarjuk kiolvasni, vagy beírni – nem lesz igazán nehéz dolgunk.
9.1.2.
Interfész
Azon widgetek kezeléséhez, melyek valamilyen tartalom szerkesztésére szolgálnak – függetlenül attól, hogy ez a tartalom szám vagy akár szöveg, egy vagy több sort foglal el – a GTK egy interfészt (GtkEditable) definiál. Ez az interfész meghatároz számos olyan m˝uveletet (insert_text, select_text, get_position, . . . ), illetve szignált (changed, delete-text, insert-text), amiket az interfész megvalósító widgetnek implementálnia kell. Ennek eredményeként ezen widgetek egy egységes felületen (API) keresztül kezelhet˝oek és csak a specifikumok tekintetében kell az interfészt implementáló widgethez tartozó függvényt, szignált használnunk.
9.2.
Alapmuveletek ˝
9.2.1.
Létrehozás
A létrehozás formai elemei a korábbi részek ismeretében nem okozhatnak meglepetést, így a tartalmi elemekre koncentrálunk. Mindkét widget esetében van mire. GtkEntry A GtkEntry osztály esetében létezik egy paraméter nélküli konstruktor függvény (new), ami voltaképpen semmit különöset nem tesz. Létrehoz egy olyan GtkEntry objektumot, ami a tulajdonságinak alapértelmezett értékeit veszi fel, ami pontosan megfelel a hétköznapi használat szükségleteinek. A másik konstruktor függvény egy GtkEntryBuffer objektumot vesz át paraméterként, aminek segítségével lehet˝ové válik a buffer által tárolt adat megjelenítése több különböz˝o GtkEntry példányban. A GtkEntryBuffer voltaképpen az egysoros beviteli mez˝o adattárolója, az ehhez szükséges kevés számú tulajdonsággal (text, length, max-length) és szignállal (deleted-text, inserted-text), valamint az ezen tulajdonságok beállítására, lekérdezésére – a szokásos nevezéktan szerint –, illetve a szignálok kiváltására szolgáló függvényekkel. GtkSpinButton A GtkSpinButton, bár a GtkEntry osztályból származik, annak specializációja, attól mégis jelentékeny mértékben eltér, mind a létrehozás, mind a kés˝obbi kezelés tekintetében. Létrehozására egy három paraméteres függvény szolgál, aminek minden paramétere szorul némi magyarázatra. adjustment A GtkAdjustment egy olyan valós számot reprezentál, ami nem csupán egy magában álló érték – hiszen erre megfelelne egy egyszer˝u gdouble is –, hanem bizonyos paraméterekkel van összerendelve. Ezek a paraméterek, a konstruktor függvénynek való átadás sorrendjében, a következ˝ok. value A konkrét érték. lower, upper Az érték által felvehet˝o minimális, maximális érték. step increment, page increment Az érték felhasználó által történ˝o növelésekor, csökkentése során használandó lépésköz. Konkrétan a le, fel, valamint a page down, page up billenty˝uk lenyomásakor a step increment, illetve a page increment értékével csökken, illetve n˝o az érték. A widget maga is tartalmaz egy fel-le nyíl párost, ami az érték növelésére, csökkentésere szolgál és szintén a step increment értéket használja. page size A GtkSpinButton esetében nem használt beállítás. digits A widget által az érték megjelenítésekor használt tizedes jegyek száma.
61
climb rate Az érték felhasználó általi ismétl˝od˝o növelésekor, csökkentésekor bizonyos lépésszám után használandó gyorsítási arány. Gyakorlatban a folyamatosan lenyomott le, fel, page down, page up billenty˝uk, illetve a widget fel le nyilai hatására lép életben ez a mechanizmus.
9.2.2.
Tartalom kezelése
A tartalom kezelése alapvet˝oen kétféleképp történhet; szöveg szerint, illetve érték szerint. Természetesen a GtkEntry esetén csak a szöveg elérése lehetséges, míg a GtkSpinButton esetén mindkét mód elérhet˝o, lévén a GtkSpinButton voltaképpen GtkEntry. A függvények nevei értelemszer˝uen GtkEntry esetén get_text, illetve set_text, a get_value, set_value, illetve get_value_as_int, set_value_as_int.
9.2.3.
Csak olvasható mód
Egy alapvet˝oen adatok bekérésére szolgáló widget esetén a szerkeszthet˝oség tiltása ugyan ritka, de nem szokatlan m˝uvelet. Ennek lehet˝oségét a korábban már említett – a GtkEntry és GtkSpinButton által is implementált – GtkEditable interfész biztosítja. Értelemszer˝uen a set_editable függvény az, amit hívva a szerkeszthet˝oséget állíthatjuk.
9.2.4.
Jelszavak kezelése
A jelszavak kezelésének szempontjából két függvényt, illetve az általuk módosított tulajdonságot kell megismernünk. Az els˝o tulajdonság visibility, ami alapértelmezetten TRUE értéket vesz fel –, meghatározza, hogy a beviteli mez˝oben, az annak értékül adott szöveget fogjuk látni, vagy ehelyett az azzal megegyez˝o számú egyforma karaktert. Utóbbi esetben ez a karakter, a másik megemlítend˝o tulajdonság (invisible-char) révén adható meg. Ennek alapértelmezett értékét a GTK határozza meg a rendszeren elérhet˝o bet˝ukészleteknek megfelel˝oen.
9.2.5.
Szignálok
A szignálok tekintetében általában csupán a beviteli mez˝o tartalmának módosítása az, ami az érdekl˝odésünkre számot tart. Ez a változás ugyanakkor többféle is lehet. Egyrészr˝ol szövegszer˝u változás egy szöveges beviteli mez˝o esetén, az érték változása szám bevitelére szolgáló widget esetén. Ez utóbbi esetben megkülönböztethet˝oek azok az helyzetek, amikor a szöveg áírása vezetett az érték változásához, illetve az az eset, amikor a widget saját funkcióit (fel/le nyíl, page up/down billeny˝uk, . . . ) kihasználva növeljük, illetve csökkentjük az értéket. Szöveg változása A GtkEditable típus – ezzel együtt tehát az interfészt implementáló GtkEntry, illetve az abból származó GtkSpinButton – changed szignálja minden esetben kiváltódik, amikor a beviteli mez˝o tartalma megváltozik, pontosabban megváltozott. A szignált kezel˝o függvény már csak azt követ˝oen hívódik meg, miután a szöveg már megváltozott, vagyis a szignálkezel˝o függvényben lekérdezve már az új értéket kapjuk. Mivel a szignál voltaképpen minden billenty˝u leütést követ˝oen– legyen az beírás vagy törlés – meghívódik, célszer˝u a kezel˝ofüggvényben implementáltak kapcsán ezt figyelembe venni, például a lefutás id˝oigényét a lehet˝oség szerinti minimumon tartani. Még ha a kód lefutása gyors is, attól érdemes óvakodni, hogy minden egyes billenty˝u lenyomására végrehajtsuk azt a m˝uveletet (validáció, más felületi elemek változtatása, . . . ), amit elegend˝o akkor megtennünk, ha a beviteli mez˝o elvesztette a fókuszt. Az erre alkalmas szignál is adott (focus-out), a bevitt adatokra vonatkozó validáció (pl: IP cím, reguláris kifejezés), vagy éppen a tartalom egyéb widgetekre gyakorolt hatása szabályainak célszer˝uen amúgy sem magából a hatásból kell kiderülniük. Erre alkalmas eszköz inkább valamely súgó, ami egy kés˝obbi részben kerül ismertetésre, ugyanakkor a hiba jelzésére használhatóak a korábban ismertetett ikonok. Érték változása A GtkSpinButton két szignállal is szolgál a tárolt érték megváltozásának jelzésére. A value-changed minden olyan esetben kiváltódik, miután a tárolt érték megváltozik, bármi legyen is annak az oka, míg a change-value szignál csak abban az esetben ha a GtkSpinButton tartalma nem szöveg beírásának hatására, hanem a billenty˝uzetr˝ol történ˝o vezérlés (fel/le nyilak, page up/dowm, . . . ) következményeként változik meg.
9.3.
Haladó muveletek ˝
9.3.1.
Ikonok
Beviteli mez˝onkben lehet˝oség van ikonok megjelenítésére a szövegmez˝o mindkét oldalán. Ugyanakkor nem csupán megjelenítésr˝ol van szó, hiszen az ikonok különböz˝o m˝uveleteit is kezelni tudjuk, ami számos hasznos, kényelmes és
62
látványos funkció megvalósítására ad lehet˝oséget.
9.2. ábra. Ikonok használata szövegbeviteli mez˝oben
keresés. Egy-egy ikonnal jelezhetjük a beviteli mez˝o két oldalán, hogy a beírt értéket keresni szeretnénk valamely tartalomban – legyen az egy több soros beviteli mez˝o, egy fájl, vagy bármi más –, míg a másik ikon szolgálhat a keresési kifejezés törlésére. jelszóbevitel. A jelszavak bevitelekor ikonnal jelezhetjük, hogy a megjelen˝o karakterek nem véletlenül nem az általunk beírtakat mutatják, a másik ikon pedig figyelmeztetésül szolgálhat, hogy ha a caps lock be van kapcsolva1 . fájlmuveletek. ˝ Fájlnevek bevitelekor jelezhetjük a felhasználó felé, hogy e mez˝oben szerepl˝o fájlév mire szolgál majd voltaképp. Ezen túlmen˝oen az ikonra kattintva felhozhatjuk a fájlválasztásra szolgáló dialógust.
9.3.2.
Folyamatindikátor
A beviteli mez˝o háttereként használhatunk folyamatindikátort, amivel jelezhetjük egy – a beviteli mez˝o tartalmával összefügg˝o – folyamat állását. Amennyiben a folyamatot magát akarjuk érzékeltetni, viszont nem ismerjük annak végét, illetve aktuális állását, akkor választhatunk egy olyan módot ahol a folyamatindikátor a beviteli mez˝o két vége között pulzál, ahol a progress_pulse függvény hívásával mozdíthatjuk tovább a folyamatindikátort a progress-pulse-step tulajdonság által meghatározott (0 és egy közötti érték) mértékben.
9.3. ábra. Ikonok használata szövegbeviteli mez˝oben Amennyiben a folyamat állapota pontosan ismert, akkor az ábrához hasonló eredmény – ahol az indikátor a folyamat állapotának mértékében foglalja el a hátteret –, a progress-fraction tulajdonság állításával érhet˝o el, ahol az érték 0 és 1 között a folyamat készenléte.
9.3.3.
Iránymutató szöveg
Egy beviteli mez˝ok esetén – legyen az szöveg vagy szám bevitelére szánva – nincs kevésbé felhasználóbarát viselkedés, mint hogy a widget semmilyen utalást nem tartalmaz arra nézvést, mit is kellene voltaképp tartalmazni. Erre természetesen alkalmas megoldás az el˝oz˝o részben megismert címke, illetve súgóbuborék, de a GtkEntry, valamint az abból származó widgetek is rendelkeznek egy említésre érdemes megoldással. Ez pedig nem más, mint hogy a beviteli mez˝obe egy olyan szöveget írhatunk, ami addig látszik, amíg felhasználó a mez˝obe írni nem kezd, vagy az írás végeztével üresen hagy. Ez a szöveg szolgálhat mintául a beírandó adat tartalmára, formájára és teszi ezt anélkül, hogy felhasználói interakciót igényelne, mint a súgóbuborék. Ezen szöveg a megadására az GtkEntry osztály set_paceholder_text függvénye szolgál.
9.3.4.
Buffer
A GtkEntry típus esetén az adattároló réteg (model) elválasztásra került a megjelenítést, illetve vezérlést (view, controller) végz˝o rétegt˝ol, ami a tulajdonképpeni GtkEntry. Egy GtkEntry létrehozható paraméterek nélkül, vagy adattárolást végz˝o típus – a GtkEntryBuffer – egy példányának megadásával. Ez egyrészt lehet˝ové teszi, hogy több, a megjelenítést végz˝o widget osztozzon ugyanazon az adattárolón eltér˝o kijelölés, vagy éppen kurzorpozíció mellet. Másrészr˝ol leszármazva a GtkEntryBuffer buffer típusból, magunk is implementálhatunk adattárolót melyek a legkülönböz˝obb igényeknek tehetnek eleget. 1 Ezt
funkciót készen kapjuk a GtkEntry esetén, be-, illetve kikapcsolása a caps-lock-warning tulajdonság állításával lehetséges.
63
9.3.5.
Formázás
A GtkSpinButton esetén magunk is meghatározhatjuk, hogy a tárolt adatot milyen formátumban fogadjuk el, illetve milyen formában jelenítjük meg. Ehhez nem kell egyebet tennünk, mint az input, illetve az output szignálokra megfelel˝o kezel˝o függvényeket kötni. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
staticgint on_input (GtkSpinButton *spin, gdouble *new_val) { gchar *err = NULL;
staticgboolean on_output (GtkSpinButton *spin) { GtkAdjustment *adjustment = gtk_spin_button_get_adjustment (spin); gdoublevalue = gtk_adjustment_get_value (adjustment); gintdigits = gtk_spin_button_get_digits (spin); gchar *buf = g_strdup_printf ("%0.*f", digits, value);
*new_val = g_strtod (gtk_entry_get_text (GTK_ENTRY (spin)), &err); if (*err) returnGTK_INPUT_ERROR;
if (strcmp (buf, gtk_entry_get_text (GTK_ENTRY (spin)))) gtk_entry_set_text (GTK_ENTRY (spin), buf); g_free (buf);
returnTRUE;
returnTRUE; }
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Kódrészlet 9.1. Az input és output szignálok felüldefiniálása az alapértelmezett m˝uködést implementálva 2. sor Az input és az output szignál kezelésére szolgáló függvény annyiban tér el egymástól, amennyiben a feladat. Az input szignált kezel˝o plusz paramétere (new_value) – az input kezel˝o függvényéhez képest –, a szövegként elérhet˝o érték lebeg˝opontos számként történ˝o visszaadására szolgál. 15. sor A visszatérési érték TRUE értéke mindkét esetben azt jelenti, hogy a szignált sikeresen kezeltük, az átalakítást elvégeztük. A FALSE visszatérési érték esetén a hívó az alapértelmezett átalakító függvényt – aminek m˝uködése azonos a bemutatottal – hívja meg. Az input szignál esetén lehetséges még egy visszatérési érték (GTK_INPUT_ERROR), amit arra használunk, hogy a hívó felé jelezzük, a GtkSpinButton szövege nem értelmezhet˝o. Ezért is gint ezen függvény visszatérési értékének típusa, míg a másiké gboolean, hisz ott csak azt közölhetjük a GtkSpinButton a beírást azt általunk kívánt formátumban megtettük-e vagy sem. 11. sor Az input esetén alapértelmezetten a GtkSpinButton szövegét decimális számrendszerbeli tizedes törtté próbáljuk alakítani. Ha ez sikerül TRUE értékkel térünk vissza, ha nem, akkor a visszatérési érték GTK_INPUT_ERROR jelezvén, hogy kezeltük az input szignált, de az átalakítás sikertelen volt, így az alapértelmezetten input szignált kezel˝o függvényt szükségtelen meghívni. Az output szignál kezelésekor épp ez el˝obbiek ellenkez˝oje történik. A GtkSpinButton aktuális számszer˝u értékét alakítjuk a megfelel˝o formátumú szöveggé, ez esetben a digits tulajdonságnak megfelel˝o számú tizedes jeggyel decimális formában. Ezt írjuk vissza a beviteli mez˝obe. A visszaírás viszont csak akkor történik meg, ha az a korábbi értékt˝ol különbözik. Az input és output szignálok felhasználására kézenfekv˝o példa lehet, hogy ha nem decimális számrendszerben szeretnénk a számokat megjeleníteni. Ez esetben a g_strtod függvény helyett – ami csak tízes számrendszerbeli számokkal m˝uködik – a g_strtoull függvény használható, ami a paraméterként kapott számrendszerrel képes dolgozni, illetve a g_strdup_printf függvény esetén a formátum leírót kell a számrendszernek megfelel˝oen módosítani (pl: %.*x tizenhatos számrendszer esetén). Ett˝ol tágabban is értelmezhetjük a be-, illetve kimenet módosítását. Lehet˝oségünk van akár arra is, hogy a GtkSpinButton ne számokat tároljon, hanem szövegeket, például a hónapok neveit.
64
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
staticgint on_input (GtkSpinButton *spin, gdouble *new_val) { staticgchar *month[12] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };
staticgint on_output (GtkSpinButton *spin) { staticgchar *month[12] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; GtkAdjustment *adjustment = gtk_spin_button_get_adjustment (spin); gintvalue = gtk_adjustment_get_value (adjustment); constgchar *text = gtk_entry_get_text (GTK_ENTRY (spin));
constgchar *text = gtk_entry_get_text (GTK_ENTRY (spin)); gint i; for (i = 1; i <= sizeof (month) / sizeof (*month); i++)
if (value && strcmp (month[i - 1], text)) { gtk_entry_set_text (GTK_ENTRY (spin), month[i - 1]);
if (!strcmp (month[i - 1], text)) { *new_val = i; returnTRUE; }
}
*new_val = 0.0; returnGTK_INPUT_ERROR;
returnTRUE; }
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
Kódrészlet 9.2. Az input és output szignálok felüldefiniálása hónapok megjelenítéséhez Ebben az esetben nem történik más, mint hogy a formázást némiképp továbbgondoljuk. Az input szignál kezelésekor – ahogy a korábbi példában is –, megpróbáljuk a beírt szöveget a formátumnak megfelel˝oen értelmezni. Ez korábban annyit jelentett, hogy tizenhatos számrendszerbeli számként próbáltuk a szöveget értelmezni, most viszont – mivel az értékkészletünk kicsi – ellen˝orizzük, hogy a beírt szöveg az értékkészlet (hónapok nevei) valamelyik eleme-e. Ha igen, akkor a hónap sorszáma lesz az új értékünk, ha nem akkor GTK_INPUT_ERROR értékkel térünk vissza. A formázás – vagyis az output szignál kezelése –, során mindössze az aktuális értéknek megfelel˝o szöveget kapja értékül a GtkSpinButton, feltéve ha eddig nem ez volt az értéke. Természetesen egy ilyen formázási megoldásnál célszer˝u a GtkSpinButton minimum és maximum értékét az általunk kezelt értékek számosságának megfelel˝oen beállítani, ami a hónapok esetén egyet jelent, mint minimum értéket és tizenkett˝ot, mint maximumot.
9.4.
Tesztelés
Lévén a beviteli mez˝ok a leggyakrabban használt widgetek, tesztelésük is épp ily gyakran fordul el˝o. Ennek kapcsán megismerkedünk néhány – a tesztelés során használt – alapfogalommal, amik a kés˝obbiekben is folyamatosan visszatérnek.
9.4.1.
Keresés
Az ebben a részben tárgyalt két widgettípushoz – az szöveg-, illetve számbeviteli mez˝o – tartozó objektumok keresésekor a GenericPredicate roleName paraméterének text, illetve spin button adandó meg. A GtkEntry kereséséhez létezik egy specializált Predicate osztály is, a IsATextEntryNamed, aminek egyetlen paramétere a beviteli mez˝o neve.
9.4.2.
Interfészek
Írható szövegek Az adatbevitelre szolgáló widgetekb˝ol értelemszer˝uen nem, vagy nem csak kiolvasni szeretnénk értéküket, hanem írni is, így ismerve a text interfész adta lehet˝oségeket, ehhez egy másik interfészre lesz szükségünk. Az ATK EditableText néven definiálja azt az interfészt ami lehet˝ové teszi, hogy szövege írjunk, szúrjunk be, vagy illesszünk be a beviteli mez˝obe. A legegyszer˝ubb esetben, amikor csak egy adott szöveggel szeretnénk felülírni a beviteli mez˝o aktuális tartalmát erre az interfészre nem lesz szükségünk, a korábban a text interfésznél ismertetett text attribútumnak való értékadás pontosan úgy m˝uködik, ahogy azt elvárjuk. Természetesen ennek megvan a megfelel˝oje a queryEditableText függvényhívás révén beszerezhet˝o editable text interfészben is, mégpedig a setTextContents függvény, aminek paraméterül a beírandó szöveget kell átadnunk. Szöveg beszúrására is van lehet˝oség a insertText függvénnyel, aminek paraméterei a beszúrás pozíciója, a beszúrandó szöveg, illetve a beszúrandó karakterek száma. A vágólap m˝uveletek szintén ezen az interfészen keresztül érhet˝oek el. A másolást (copyText), törlést (deleteText), illetve kivágást (cutText) végz˝o függvényi két paramétert vesznek át, a szövegrész kezd˝o-, illetve végpozícióját amin a m˝uveletet végre akarjuk hajtani. A beillesztés (pasteText) függvénynek csak egy paraméterre, a beillesztés helyére van szüksége. Számok Azon widgettípusokat, amiket számok bevitelére használunk nyilvánvalóan a tesztelés során is így szeretnénk kezelni, még akkor is ha voltaképpen szövegként is kiolvashatnánk, vagy beírhatnánk az adatot, aztán a konverziót megejthetnénk 65
magunk. Az ATK szerencsénkre definiál a számok kezelésére használatos interfészt (AtkValue), így ezzel különösebb gondunk nem is lesz. Az interfész azonban nem csupán annyit nyújt, hogy számként írhatjuk, olvashatjuk az objektum tartalmát, de lehet˝oséget ad a mez˝o által elfogadott intervallum alsó, illetve fels˝o határának kiolvasására is. Az interfész lekérdezése a már megszokott elnevezési konvenció szerinti függvénnyel történik (queryValue), rajta keresztül az intervallum alsó határát a minimumValue, fels˝o határát a maximumValue, míg aktuális értékét a currentValue attribútumon keresztül érhetjük el. El˝obbi kett˝ot csak olvasásra, míg utóbbit írásra és olvasásra egyaránt. Gyakori esetr˝ol lévén szó ezen feladatok a Dogatil Node osztályán keresztül is elvégezhet˝oek, rendre a minValue, maxValue, illetve a value attribútumok segítségével. Ez utóbbi megoldás el˝onye, hogy megspórolhatjuk az interfész lekérdezését, illetve nem kell e kivételkezeléssel sem foglalkoznunk, mivel ha az interfész nem implementált, akkor lekérdezéskor a NotImplementedError kivétel helyett egyszer˝uen csak None értéket kapunk.
9.4.3.
Állapotok
Mivel a beviteli mez˝oket tárgyaltunk ebben a részben, amik természetüknél fogva írhatóak, ugyanakkor lehet˝oség van arra a szerkeszthet˝oség letiltására, kézenfekv˝oen adódik, hogy ezt az állapotot le is lehessen kérdezni. Ezt minden további nélkül meg is lehet tenni a már ismert getState függvénynek a pyatspi.EDITABLE értéket megadva paraméterként. Egy másik állapot is kézenfekv˝oen adódik a tárgyalt widgetek típusából, mégpedig, hogy csak egy sornyi adat fogadására képesek, vagyis a pyatspi.SINGLE_LINE állapot mindig része a widget állapothalmazának, míg a pyatspi.MULTI_LINE sosem.
9.4.4.
Tulajdonságok
Mivel sem a text, sem pedig az editable text interfész nem ad módot rá módot, a már említett tulajdonságok (attribute) közül olvasható ki a GtkEntry osztály set_paceholder_text függvénye révén beállított szöveget, placeholder-text kulcs alatt.
9.4.5.
Akciók
A gombokhoz hasonlóan a beviteli mez˝ok is rendelkeznek egy rajtuk kiváltható akcióval, ez pedig a az alapértelmezett widget aktiválása. Amennyiben a vonatkozó tulajdonság (activates-default) igaz, az Node objektumon az activate akció.
66
10. fejezet
Választáson alapuló adatbevitel A szöveges adatbevitel leginkább kézenfekv˝o módjának tárgyalása már megtörtént. Ott a szöveget szabadon, illetve a validációnak megfelel˝oen választhattuk meg, ezúttal viszont olyan adatok bevitelével foglalkozunk, ahol az értékkészlet lényegesen sz˝ukebb, mint ami egy GtkEntry, vagy akár egy GtkSpinButton esetén szokásos. Az ebben a részben bemutatandó widgetek akkor használatosak, ha a választható értékek száma kicsi, adott esetben csupán kett˝o, de nem több, mint egy tucat.
10.1.
Fogalmak
10.1.1.
Beviteli mez˝ok típusai
Ahogy arról szó volt a szöveges bevitelr˝ol szóló részben, speciális esetek speciális widgeteket kívánnak, lévén az ilyen helyzetekben maguk a widgetek jóval inkább testreszabhatóak, mint egy általános helyzetnek is megfelel˝o szöveges beviteli mez˝o. A testreszabott widgetek megjelenése, beviteli módszereik könnyen alkalmazkodhatnak az igényekhez. Bináris változók A specializáció egyik véglete, amikor a bevinni kívánt adat mindössze két értéket vehet fel. Az ilyen bináris adatok szép számban fordulnak el˝o, legyen szó akár egy opció ki-, vagy bekapcsolt értékér˝ol, vagy más igaz hamis jelleg˝u adatról.
10.1. ábra. Jelöl˝onégyzet[9]
Az opciókat érdemes lehet csoportokba szervezni, ahol az egyes elemek valamilyen logika mentén együvé tartoznak, ugyanakkor az általuk felvett értékek függetlenek egymástól. Ilyen helyzetekben is alkalmazható ez a widgettípus, lehet˝oség szerint függ˝oleges elrendezésben, nyolcnál nem nagyobb elemszámú csoportokban. Egymást kizáró elemek Kis elemszám. Olyan esetekben, amikor néhány lehet˝oség – ha mód van rá, nem több, mint nyolc – közül választhatunk oly módon, hogy az egyes esetek kölcsönösen kizárják egymást, célszer˝u a felhasználónak minden lehet˝oséget megmutatni, megkönnyítend˝o a választást, viszont nem engednénk meg, hogy egyszerre egy elemnél többet ki lehessen választani. Közepes elemszám. Amennyiben az elemek száma meghaladja az imént említetteket, a felhasználói felületen történ˝o elhelyezés – úgy, hogy az elemek egyid˝oben láthatóak legyenek – már nehézkes. Ezen oknál fogva olyan megoldás alkalmazandó, ahol alapvet˝oen csak az aktuálisan kiválasztott elem látszik, ugyanakkor mód van az összes lehet˝oség áttekintésére is.
67
10.2. ábra. Rádiógombok[9]
10.3. ábra. ComboBox[9]
Választott érték Nincs választás. Azon esetekben, ahol nem tudunk megfelel˝o alapértelmezett értéket adni indulásképp, lehet˝oség van arra, hogy aktuálisan ne legyen egyetlen lehetséges elem sem kiválasztott állapotban. Ezt a helyzetet kezelnünk kell, vagyis az bevitt adatok validációjának erre a helyzetre is ki kell terjednie. Inkonzisztens állapot. A bináris adatok bevitelére – egyúttal természetesen megjelenítésére is – szolgáló widgetek lehetséges állapotainak száma, eltér˝oen attól amit els˝ore gondolnánk, három. A két kézenfekv˝o állapoton túl létezik egy inkonzisztens állapot is, ami sem egyik sem másik állapotnak nem felel meg. Gyakorlatban ennek akkor van jelent˝osége, amikor eltér˝o értékeket egyszerre szeretnénk megmutatni.
10.4. ábra. Inkonzisztens állapot jelöl˝onégyzetek esetén[6]
10.2.
Alapmuveletek ˝
10.2.1.
Létrehozás
A létrehozás nem rejt magában különösebb bonyodalmakat, hiszen maguk a widgetek is rendkívül egyszer˝unek mondhatók, ugyanakkor egy-egy gondolat erejéig mégis érdemes megállni. GtkCheckButton Az ehhez a típushoz tartozó widgetek létrehozásánál érdemben csak egy paramétert kell megadnunk, mégpedig a jelöl˝onégyzet szövegét. A szöveggel szemben azonban támasztunk néhány követelményt, amiket célszer˝u a felhasználói felület készítése közben figyelembe venni. Az els˝o kifejezetten a szövegre vonatkozik, ami szerint a szöveg els˝o szavát nagy-, míg a többit kisbet˝uvel kezdjük. A másik csak közvetetten vonatkozik a szövegre magára, annyi lenne az elvárás, hogy minden jelöl˝onégyzet állítható legyen közvetlenül billenty˝uzetr˝ol is, amit a címkéknél már említett módszerrel (mnemonic) érhetünk el.
68
GtkWidget* gtk_check_button_new (void);
CheckButton();
classCheckButton(Gtk.ToggleButton):
GtkWidget* gtk_check_button_new_with_label (constgchar *label);
explicitCheckButton(constGlib::ustring& label boolmnemonic = false);
def__init__(self, label=None, stock=None, use_stock=False, use_underline=False, **kwds):
GtkWidget* gtk_check_button_new_with_mnemonic (constgchar *label);
Kódrészlet 10.1. CheckButton létrehozása A mnemonic, valamint a use_underline paraméterek célja azonos, a címkéknél korábban említett módszer – miszerint a közvetlen elérést biztosító billenty˝ut aláhúzás jel (_) el˝ozi meg – engedélyezésére szolgálnak. A use_underline tulajdonság hamis értéke esetén az aláhúzás jelként látszódik, tehát a közvetlen eléréshez a use_underline tulajdonság értékének igaznak kell lennie. A Python nyelv˝u változat a konstruáláskor lehet˝oséget ad a beépített ikonok (stock icon) megadására, akár a label paraméter értékeként, amennyiben a use_stock paraméter értéke igaz. Amennyiben az utóbbi érték hamis a label paraméter értéke szó szerint jelenik meg, akkor is ha az egy beépített ikon neve is egyben. A GtkRadioButton Mivel a rádiógomb típus közvetlen leszármazottja az imént tárgyalt GtkCheckButton típusnak, nem meglep˝o módon létrehozása is nagyban hasonlít a jelöl˝onégyzetek létrehozásával, legalábbis ami a címke szövegét illeti. Mivel ezt a widgetet csoportban használjuk – lévén egymást kizáró lehet˝oségek közül választunk – a csoportot valamilyen módon meg kell tudnunk adni. A gyakorlatban a rádiógomb-csoportok kódból történ˝o létrehozása úgy történik, hogy az els˝o rádiógombot úgymond önmagában, csoport nélkül hozzuk létre, a további rádiógombok esetén pedig az els˝o elem létrejöttekor automatikusan elkészült csoportot használjuk fel. A csoportot a get_group függvény segítségével kérdezhetjük le és a visszakapott csoportot adhatjuk át az újabb rádiógombok létrehozásakor paraméterként. Ha a rádiógomb már korábban elkészült, vagy a címke megadásával hoztuk létre, akkor a csoport természetesen utólag is átállítható (set_group). GtkComboBoxText Amennyiben az elemek túl nagy száma, vagy a helyhiány miatt úgy döntünk, hogy az egymást kizáró elemek közül nem rádiógombok csoportjából egyet megjelölve akarunk választani, akkor az elemeket listába szervezhetjük, ahol csak az aktuálisan kiválasztott elem jelenik meg a felületen. A létrehozás során csupán egy döntést kell meghoznunk, hogy az elem közötti választást lehet˝ové tesszük-e úgy is, hogy az elem szövegét a felhasználó egy egysoros beviteli mez˝obe (GtkEntry) gépelhesse be. GtkWidget* gtk_combo_box_text_new (void);
explicitComboBoxText (boolhas_entry = false);
classComboBoxText(Gtk.ComboBox): def__init__(self, **kwds): @staticmethod@ defnew_with_entry():
GtkWidget* gtk_combo_box_text_new_with_entry (void);
Kódrészlet 10.2. ComboBoxText létrehozása Amint látszik az egyes nyelvi változatok valamelyest eltérnek egymástól, ám az eltérés nem számottev˝o. Voltaképpen a nyelvi különböz˝oségeknek megfelel˝o konstruktorok megvalósításokról beszélünk. A C++ változat esetén paraméterként vesszük át annak értékét, hogy a ComboBoxText tartalmazzon-e beviteli mez˝ot, vagy csak választani lehessen az elemek között, míg a C változat erre két függvényt ad, ahogy a Python is1 .
10.2.2.
Kezelés
GtkCheckButton Billentyuzetr˝ ˝ ol történ˝o használat. A használhatóság szempontjából fontos kiemelni, hogy minden jelöl˝onégyzetnek adjunk meg közvetlen elérést biztosító billenty˝ut (mnemonic key), mivel az nagyban könnyíti és gyorsítja a billenty˝uzetr˝ol történ˝o használatot. Ez természetesen nem csak a GtkCheckButton, de az abból származó GtkRadioButton esetén is igaz. GtkComboBoxText Elemek hozzáadása. Elemek hozzáadására alapvet˝oen három mód kínálkozik, amib˝ol kett˝o tulajdonképpen a harmadik specializált esete. Adhatunk új elemet a már meglév˝o elemek elé (prepend_text), mögé (append_text), vagy beszúrhatjuk az a már meglév˝o elemek közé (insert_text). El˝obbi két esetben csupán a szöveget kell megadnunk, hiszen itt 1 a Python változat __init__ függvénye is tartalmazhatna alapértelmezett paramétert a beviteli mez˝ ore vonatkozóan, viszont a C változathoz a két külön metódus (a konstruktor és egy statikus metódus) áll közelebb
69
az új elem pozíciója adott, míg a harmadik esetben paraméterként az új elem pozícióját is meg kell adni. Amennyiben pozícióként nullánál kisebb értéket adunk meg, akkor az új elem a meglév˝o elemek mögé, amennyiben nullát, akkor a meglév˝o elemek elé, míg pozitív szám esetén az adott pozícióra szúródik be. Elemek törlése. Elemek törlésére szintén két mód kínálkozik. Egyrészr˝ol törölhetünk egyes elemeket a pozíciójuk alapján (remove), vagy törölhetjük egyszerre akár az összes elemet is (remove_all). Kiválasztott elem. A kiválasztott elem szövegének lekérdezésére a get_active_text függvény szolgál, aminek m˝uködése kézenfekv˝onek t˝unhet, mégsem az, mivel az implicit módon függ a widget bizonyos állapotaitól. Egyrészr˝ol, ha a widget rendelkezik saját beviteli mez˝ovel (10.2), akkor minden esetben az abban lév˝o értékkel tér vissza, ha nem, akkor a kiválasztott elem szövegével, illetve ha nincs kiválasztva elem, akkor a nyelvi változatnak megfelel˝o értékkel. Ez Python esetén egy None érték, a C esetén egy NULL pointer, míg a C++ esetén egy üres Glib::ustring objektum.
10.2.3.
Szignálok
GtkRadioButton A GtkRadioButton, pontosabban szólva annak o˝ se a GtkToggleButton mindössze egy szignállal rendelkezik (toggled), ami a gomb állapotának – vagy ha úgy tetszik értékének – megváltozása után váltódik ki, ahogy az a nevéb˝ol és a korábban ismertetett hasonló célt szolgáló szignálok m˝uködéséb˝ol is következik. A szignál különösebb figyelmet csak a rádiógombok esetén érdemel, mivel itt midig két widget szignálja váltódik ki közvetlenül egymás után. Ez annak következménye, hogy az azonos csoportba tartozó rádiógombok kölcsönösen kizárják egymást, így ha az egyikük aktívvá válik, akkor a korábban aktív állapotú gomb elveszti aktív státuszát. A szignálok kezelésekor tehát ügyelni kell arra, hogy ezen duplán megkapott szignálok esetén a megfelel˝o m˝uveleteket csak egyszer hajtsuk végre.
10.3.
Haladó muveletek ˝
GtkCheckButton Inkonzisztens állapot. A GtkToggleButton típusból származó osztályok (GtkCheckButton, GtkRadioButton) esetén az inkonzisztens állapot (10.1.1) használatakor arra kell tekintettel lennünk, hogy ezen állapot beállítása csak a widget megjelenítésére van hatással, az aktív állapot értékét nem változtatja meg. Ezen túlmen˝oen az inkonzisztens állapot megszüntetésér˝ol is magunknak kell gondoskodnunk, mivel azt a felhasználói interakció (egérkattintás, gyorsbillenty˝u, . . . ) nem módosítja, annak hatására csak az aktív állapot változik, pontosan ugyanúgy váltakozik, mintha az inkonzisztens állapot be sem lenne állítva.
10.4.
Tesztelés
10.4.1.
Keresés
Az ebben a részben tárgyalt widgettípusokra vonatkozó specifikus Predicate osztály nincs, így a findChild függvény esetén a GenericPredicate osztályt használhatjuk, ahol a jelöl˝onégyzet esetén check box, rádiógomb esetén radio button, a GtkComboBoxText osztály esetén pedig combo box adandó meg a roleName paraméter értékeként.
10.4.2.
Státuszok
A tárgyalt widgetek (a GtkToggleButton és annak leszármazottjai) esetén gyakorlatilag egy érdemleges kérdés van, mégpedig a widget aktuális állapota. Ez egyrészr˝ol lekérdezhet˝o az ablakok kapcsán már ismertetett getState függvényt használva, a függvények pyatspi.STATE_CHECKED állandót átadva paraméterként, illetve a Dogtail Node objektum check attribútumának kiolvasásával. Az inkonzisztens állapotra a Dogtail egyel˝ore nem ad burkolófüggvényt, így marad a getState függvény hívása pyatspi.STATE_INDETERMINATE paraméterrel.
10.4.3.
Akciók
A jelöl˝onégyzet és a rádiógomb értékének módosítására a click akciót használhatjuk, amit vagy az action interfészen keresztül érhetünk el, vagy egész egyszer˝uen meghívjuk a Node objektum click függvényét.
70
10.4.4.
Viszonyok
A rádiógombok mindegyike rendelkezik egy olyan viszonnyal (relation), ami a többi rádiógombhoz köti, amikkel egy csoportban van, beleérte magát a rádiógombot is aminek a viszonyáról szó van. A címkéknél már említett getRelationSet, illetve getTarget függvényekkel az összetartozó rádiógombok megkaphatóak, a pyatspi.RELATION_MEMBER_OF viszonytípust felhasználva.
10.4.5.
Interfészek
A GtkComboBox típus és annak leszármazottai – közöttük az ebben a részben tárgyalt GtkComboBoxText – aktív elemének lekérdezésére és beállítására több lehet˝oség is kínálkozik. Ezek közük egy a selection interfész. Az interfész azon widgetek kezelésére alkalmas, amik önmagukban több elem megjelenítésére és a köztük történ˝o választásokra használatosak, hiszen itt merül fel a kérdés, hogy a widget melyik eleme van aktuálisan kiválasztva, illetve hogy hogyan lehetne valamelyik elemet kiválasztani. Ahogy arról korábban szó esett az egyes felületi elemek által alkotott fa szerkezet2 visszaköszön a teszteléshez használt objektumoknál is, s˝ot esetenként még ennél többr˝ol is szó van. A fa hierarchia legalsó szintjén álló widgetek ugyanis nem feltétlenül állnak a tesztelés során használt Node objektumok hierarchiájának legalján. Különösen igaz az önmagukban több elemb˝ol való választást lehet˝ové tev˝o widgetek3 esetén, ahol a választható elemek valamilyen formában a Node elemeiként jelennek meg. Konkrétan a ComboBox widgetnek megfelel˝o Node mindig rendelkezik egy menu, míg az a ComboBox elemszámának megfelel˝o mennyiség˝u menu item típusú (roleName) gyerekkel. Utóbbiak közül választhatjuk ki az aktív elemet a selection interfésszel. A querySelection függvény által visszaadott interfész selectChild függvénye választja ki a megadott sorszámú gyerekelemet. Az aktuálisan kiválasztott elemek az interfész getSelectedChild függvénye segítségével kérdezhet˝oek le, ami paraméterként azt várja, hogy hányadik kiválasztott elemre vagyunk kíváncsiak. Ez az érték kisebb, mint az nSelectedChildren attribútum érték, ami az aktuálisan kiválasztott elemek számát tartalmazza. A Dogtail a Node objektum részeként is kínál számos függvényt kijelölt elemek kezelésére. Egy adott gyerekelem kiválasztása a hozzá tartozó Node (menu item) select függvényének meghívásával lehetséges. Az isSelected attribútum az adott gyerekelem kiválasztott mivoltáról árulkodik, míg egy szül˝o kiválasztott elemei4 a selectedChildren attribútum révén érhet˝oek el. Érdemleges körülmény, hogy a ComboBox widgethez tartozó Node objektum name attribútumának értéke az aktuálisan kiválasztott elem értékét veszi fel, így a név csak körülményesen használható fel a Node megkeresésekor. Kézenfekv˝oen csak a roleName használható, ami viszont egyedileg csak akkor azonosít, ha az adott részfa alatt csak ez az egy ilyen elem található, amit a részfa sz˝ukítésével tudunk eszközölni.
2a
konténerek és elemeik egymással 1 : N viszonyban állnak, összességében fa szerkezetet alkotva rádiógombok is lehet˝ové tesznek választást, de csak több azonos widget révén 4 GtkComboBox esetén az elmondottak alapján a hozzá tartozó Node gyereke – ahol a roleName menu – használandó e tekintetben 3a
71
rész III
Összetett widgetek
72
11. fejezet
Táblázatos megjelenítés alapjai Elérkeztünk a GTK egyik legrugalmasabb, legszélesebb kör˝u funkciókkal felruházható, ugyanakkor legkomplexebb eszközéhez. A widget objektumok együtteseként egyszerre teszi lehet˝ové az adatok megjelenítését, bevitelét, az elemek kiválasztást, keresését, sz˝urését, rendezését. Alapvet˝o funkciója azonos típusú objektumok tulajdonságainak táblázatos formában történ˝o megjelenítése, illetve kezelése, ahol a sorok az egyes objektumokhoz reprezentálják, míg az oszlopok az objektum egy adott tulajdonságához tartoznak.
11.1.
Fogalmak
11.1.1.
Modell-nézet-vezérl˝o
A widget m˝uködése a modell-nézet-vezérl˝o elvet követi, vagyis elválik egymástól az adatok tárolására, illetve megjelenítésére, valamint vezérlésére szolgáló réteg. Ez egyfel˝ol rugalmasságot tesz lehet˝ové a m˝uködés tekintetében, a performancia javulását is eredményezheti kell˝o körültekintés mellett, ugyanakkor sajnálatos módon bonyolítja az implementációt. Eddigiekben is találkoztunk már ezt a mintát követ˝o widgetekkel (GtkEntry, GtkComboBoxText), ugyanakkor ezen widgetek esetén – bár a háttérben ugyan ez a minta húzódik meg – a használat során ennek hatásait elkerülhetjük. Most azonban a nézet és a modell elválasztása kötelez˝o, hatási nem megkerülhet˝oek.
11.1.2.
Modell
A modell-nézet-vezérl˝o mintában a modell jelenti adattároló réteget, a GTK terminológia annyiban eltér, hogy a modell egy absztrakt osztály, ami a nézet irányába történ˝o adatszolgáltatáshoz szükséges függvényeket, illetve szignálokat írja le. A tényleges adattárolóknak ezen absztrakt osztályt kell megvalósítaniuk, vagyis saját bels˝o adatreprezentációjukat elrejtve a külvilág – ez esetben a nézetet megvalósító widget – felé a modell által definiáltak megfelel˝oen kell m˝uködniük. A GTK a modell két implementációját is biztosítja, egyet a lista, egyet a fa szerkezettel rendelkez˝o adatok számára, ezzel együtt természetesen ha a saját adattárolónkat tartjuk performancia vagy egyéb okoknál fogva alkalmasabbnak, azt minden további nélkül használhatjuk, ha implementáljuk a GtkTreeModel osztály által leírt alkalmazásprogramozási felületet (API).
11.1.3.
Nézet
A nézet (view) a megjelenítést végz˝o réteg a modell-nézet-vezérl˝o mintában. A modellben tárolt adatokat, valamint az adatok változását jelz˝o szignálokat felhasználva végzi adatok megjelenítését, számos, az adatok típusára, formátumára vonatkozó paraméternek megfelel˝oen. Maga a nézet alakítja ki a táblázatos megjelenési formát, annak oszlopaival és soraival, az ezek metszéspontjaiban található cellákkal, kezelve a megjelenítéskor azt a helyzetet, hogy a modellben tárolt adatok mennyisége többszöröse lehet annak, amit a nézet egy id˝oben megjeleníteni képes. Egy modellhez több nézet is rendelhet˝o, vagyis ugyanazon adatok több különböz˝o formában is megmutathatjuk a felhasználói felület más-más részein, vagy ugyanott az eltérések hangsúlyozása végett. A nézet különbsége jelentheti például, hogy más-más modellbeli oszlopok megjelenítése történik, más sorrendben, vagy sz˝urésnek, rendezésnek vetjük alá a elemeket, esetlen eltér˝o szövegformázási paramétereket alkalmazunk. Mindezt úgy tehetjük meg, hogy az adattároló nem kell lemásolnunk, vagyis az applikáció memóriaigénye nem n˝o.
11.1.4.
Elemek elérése
Maga a modell definiálja az adattároló elemeihez – sorok és ezen belül cellák – hozzáférését, illetve az adattároló elemeinek bejárását lehet˝ové tev˝o eszközöket, valamint módszereket. Ezen eszközök közül három is rendelkezésünkre áll az adattároló egyes elemire való hivatkozásra, amik mind alkalmazási területükben, mind m˝uködésükben eltérnek egymástól. 73
Bejáró. Az els˝o és egyben a leggyakrabban használt ezek közül a bejáró (iterator), ami az adattároló egy eleméhez – legyen az lista vagy fa – ad hozzáférést. A bejárón keresztül közvetlenül írhatjuk vagy olvashatjuk a vonatkozó sor egyes elemeit, celláit. A bejárók er˝osen köt˝odnek a hivatkozott adattárolóhoz, egy adott modellre vonatkozó bejáró más modell esetén nem használható fel. A bejáró az adott elemre (itt sor) nézve csak addig bír érvényes hivatkozással, amíg az adattároló szerkezete változatlan. Egyszer˝ubben fogalmazva egy bejáró csak addig érvényes, amíg az adattárolóhoz újabb elemet nem adunk (amivel egyébiránt újabb bejáró jön létre), vagy veszünk el bel˝ole. A tárolt adatokhoz való hozzáférés természetesen a szerkezetet nem érinti, így az sem aktuálisan hozzáférést biztosító, sem más bejárókat nem érvénytelenít. Útvonal. A modell egy eleme megadható a modellben elfoglalt pozíciójával is, ezt teszi az útvonal (path) leírására szolgáló objektum. A modellben elfoglalt pozíció szó szerint az jelenti, hogy az adott elem hányadik az eleme sorában, a C nyelvi hagyományoknak megfelel˝oen nullától kezdve a számlálást. Ha nem listában, hanem fában gondolkodunk, akkor ez annyi elemet jelent amilyen mélyen (depth) az adott sor elhelyezkedik a fában. Az elem sorozata pedig a fa gyökerét˝ol indulva az adott részfában a sor indexe.
11.1. ábra. Útvonal fa szerkezeten belül[6] Az ábrán a William Field értéket tartalmazó sor útvonalát az 1, 0, 2 sorozat írja le, hiszen a legfels˝o szinten lév˝o második elem, els˝o gyerekének, harmadik gyermeke. Az útvonal nem köt˝odik a modellhez, így egy útvonal bármelyik modellel együtt értelmezhet˝o. Ha az útvonalnak megfelel˝o sor létezik az adattárolóban, akkor visszakaphatjuk a mutatott sorhoz tartozó bejárót, amin keresztül már hozzáférhetünk az adatokhoz. Mivel nincs köt˝odés a modellel, maga az útvonal objektum mindig érvényes, csak egy adott modell esetén nem garantált, hogy az útvonalon található sor. Referencia. A referencia (row reference) olyan hivatkozás az adattároló egy sorára, ami mindaddig érvényes marad, amíg maga a sor létezik. A bejáróval ellentétben tehát hiába változik az adattároló szerkezete – hozzáadás, vagy törlés révén – a referencia érvényes marad, eltekintve attól ha épp az adott sort töröltük. A referencia hasonlóan a bejáróhoz köt˝odik az adattárolóhoz, csak egy adott modellen érvényes. Az érvényesség lekérdezhet˝o, az objektum mind útvonallá, mind pedig bejáróvá átalakítható. Ez utóbbi révén a modell adataihoz való hozzáférés is biztosított. A kényelemnek, amit ez a fajta m˝uködés ad, a performancia oldalán kell az árát megfizetnünk, így ezen elemek nagy mennyiség˝u használata sebességbeli problémákhoz vezethet.
11.2.
Alapmuveletek ˝
11.2.1.
Létrehozás
Modell A GTK által implementált – listák kezelésére használható – adattároló (GtkListStore) legegyszer˝ubben úgy képzelhet˝o el, mint egy táblázat. Ezen táblázat alapvetése, hogy egyforma típusú elemek különböz˝o példányainak soronkénti megjelenítésére szolgál, jelentsenek az elemek bár fájlokat, elektronikus leveleket, vagy éppen t˝uzfalszabályokat. A sorokban az egyes elemek tulajdonságai szerepelnek, azaz minden oszlop típusa azonos, amik soronként eltér˝o értékeket vehetnek fel. Ennek megfelel˝oen a létrehozás során meg kell adnunk az oszlopok számosságát, illetve az egyes oszlopok típusát. Ez lényegében nem jelent komoly feladatot, ugyanakkor viszont ez egyes nyelvi változatok között komoly eltérések tapasztalhatóak, ami leginkább a nyelvi sajátosságoknak köszönhet˝oek.
74
GtkListStore * gtk_list_store_new(gintn_columns, ...); staticGlib::RefPtr create(constTreeModelColumnRecord& columns);
GtkListStore * gtk_list_store_newv(gintn_columns, GType *types);
classListStore(Gtk.ListStore, TreeModel, TreeSortable): def__init__(self, *column_types):
Kódrészlet 11.1. GtkListStore létrehozása Ahogy látszik a (C nyelvi változat elvárja t˝olünk, hogy megadjuk hány oszlopa lesz az elkészítend˝o adattárolónknak (store). A változó hosszúságú paraméterlistájú függvénynél csak akkor van módunk hívottként rájönni, hogy mikor van vége a típusok felsorolásának, ha van olyan érték, amit az átvett típus (GType) elemei nem vehetnek fel. Hasonló a helyzet az átadott tömb kapcsán is. Így viszont nem marad más választásunk mint az oszlopok számát el˝ore közölni. A másik két nyelvi változat kézenfekv˝o módszereket ad egy konténer méretének, illetve az átadott paraméterek számának lekérdezésére így annak külön megadására explicit módon nincs szükség. Külön említést érdemel a függvények visszatérési értéke, azaz az adattároló nyelvi változatnak megfelel˝o reprezentációja. Mindhárom esetben igaz, hogy a visszakapott érték egy referencia-számlált objektumok, lévén a GtkListStore a GObject típusból származik, viszont ez az egyes nyelveken különböz˝o módokon jelenik meg. A C változat egy mutatóval tér vissza, ahogy az szokás a widgetek esetén is, a különbség mindössze annyi, hogy a specifikus típusra kapunk mutatót, ahogy ez a többi GObject típusból származó osztály esetén is igaz lesz. Itt a referencia növelése csak akkor történik meg, ha azt explicit módon kérjük, vagy azt adattárolóval hívott objektum – például a nézet – maga megteszi. A C++ változat egy intelligens mutatóval (smart pointer) tér vissza, ami másolásakor implicit módon maga biztosítja a referenciaszám növelését, így ezzel nekünk nem kell tör˝odnünk. A Python nyelvi szinten rendelkezik referencia-számlálási megoldással, így itt nem szükséges az objektumok allokációjának és felszabadításának részleteivel tör˝odnünk. A referencia-számlálás már csak azért is fontos, mert egy modellre több nézetet is csatolhatunk, amiknek egyenként növelik a referencia értékét, hogy biztosítsák az adattároló objektum fennmaradását addig, amíg a nézet ezt használja. Az adattároló nem widget, vagyis az alapfogalmak között említett ”lebeg˝o” referencia szerinti m˝uködés itt nincs érvényben, következésképp az adattároló referenciaszámának értéke egy lesz létrehozáskor és már ez els˝o nézet modellhez való csatlakoztatása azt eredményezi, hogy referenciaszám eggyel n˝o. Így tehát minden csatolt nézet megsz˝unése után az adattároló referenciaszámának értéke újra egy lesz, tehát az nem sz˝unik meg automatikusan. Ha azt szeretnénk, hogy a modell a nézetek megsz˝untével maga is megsz˝unjön, akkor a nézet(ek) csatolása után magunknak kell csökkentenünk a referencia értékét. Mivel a referencia-számlálás funkcióját a GObject típus adja, ennek unref függvényét kell hívnunk. typedefenum {
structModelColumns : publicGtk::TreeModel::ColumnRecord classModel(object): { ModelColumns() { add(col_id); add(col_name); }
COL_ID, COL_NAME, NUM_COLS } ModelColumns;
Gtk::TreeModelColumn col_id; Gtk::TreeModelColumn col_name;
COL_ID = 1 COL_NAME = 2 NUM_COLS = 3
};
store = gtk_list_store_new (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
ModelColumnsColumns; store = Gtk::ListStore::create(Columns);
store = Gtk.ListStore(int, str)
Kódrészlet 11.2. Példa GtkListStore létrehozására A gyakorlatban minden nyelvi változat esetén kialakult a nyelv sajátosságaihoz idomuló módszer, amivel a kés˝obbiekben a tároló könnyel kezelhet˝ové válik, aminek alapjait értelemszer˝uen a létrehozáskor tesszük le. A C nyelvi változat esetén egy sorszámozott típus (enum) tud rajtunk leginkább segíteni, ahol a típus egyes elemeinek nevei utalnak funkciójukra. Az el˝otagként használhatunk a tárolóra utaló (pl: STORE_NAMES) szöveget, majd folytathatjuk a COL rövidítéssel, ami az érték oszlopsorszám mivoltát jelenti, míg az utótag az oszlopban tárolt adatra vonatkozhat. Ezzel olyan nevet kapunk, amit a kés˝obbiekben a tárolóba való írás, illetve az onnan való olvasás esetén öndokumentálóvá teszi a kódot, ellentétben a puszta sorszámokkal, amik magic numbert alkotnak a kódban. A C++ változat maga ad egy osztályt (TreeModel::ColumnRecord) ami az oszlopok leírására szolgál. A gyakorlatban származtatás révén érjük el, hogy tároló konstruktora az épp elkészítend˝o objektumra specifikus oszlopleírót kaphasson. Ebben a származtatott osztályban nincs más dolgunk – ahogy az a példában is látszik –, mint publikus tagként – mivel a kés˝obbiekben ezen objektum adattagjait használjuk az írás, olvasás m˝uveletnél az oszlopok azonosítására – felsorolni és ezeket a konstruktorban hozzáadni az objektum privát konténeréhez (add). Ezen konténer mérete lekérdezhet˝o a size függvénnyel, aminek révén a tároló is tudni fogja voltaképpen hány oszloppal is rendelkezünk. A Python változat újfent a legegyszer˝ubb, de legalábbis a legrövidebb, így bizonyos értelemben ezzel járunk a legjobban. Újdonságot a másik két változathoz képest nem tartalmaz, megjegyezni is csak annyit érdemes vele kapcsolatban, hogy az oszlopokhoz használt saját osztály helyett ha származtatunk a GtkListStore típusból, akkor azon belül definiálhatjuk a névvel azonosított oszlopsorszámokat. A C változathoz hasonlóan itt is érdemes különös figyelmet fordítani arra, hogy az oszlopok sorszámai és a típusok helyes sorrendben legyenek, különben futás közben váratlan és nehezen visszanyomozható hibákba ütközhetünk.
75
Nézet A nézet (view) létrehozása önmagában egy rendkívül egyszer˝u feladat, hiszen ez akár paraméter nélkül is lehetséges, vagy paraméterként átadható egy modell, ami természetesen kés˝obb is beállíthatunk, vagy átállíthatunk (set_model). GtkWidget * gtk_tree_view_new (void);
TreeView();
classTreeView(Gtk.TreeView, Container): def__init__(self, model=None):
GtkWidget * gtk_tree_view_new_with_model (GtkTreeModel *model);
explicitTreeView(constGlib::RefPtr& model);
Kódrészlet 11.3. GtkTreeView létrehozása A teljes modell-nézet-vezérl˝o szerkezet nézet részének csak egy része maga a GtkTreeView, ugyanakkor a feladat érdemi részét nem ez, hanem az oszlopok hozzáadása jelenti. Amint az a modell felépítéséb˝ol következik, az oszlopok azonos típusú értékeket tartalmaznak, tehát azonos eszközzel is kell o˝ ket megjelenítenünk. Ez az eszköz az oszlop – a hozzá tartozó osztály pedig a GtkTreeViewColumn –, ami gondoskodik az oszlop, illetve fejlécének megjelenítésér˝ol, az oszlop átméretezhet˝oségér˝ol, nem gondoskodik azonban a sorok és oszlopok metszéspontjaként adódó cellák megjelenítésér˝ol, ezt egy külön osztály végzi. GtkTreeViewColumn * gtk_tree_view_column_new (void);
TreeViewColumn(); explicitTreeViewColumn(constGlib::ustring& title);
GtkTreeViewColumn * gtk_tree_view_column_new_with_area (GtkCellArea *area);
TreeViewColumn(constGlib::ustring& title, CellRenderer& cell);
classTreeViewColumn(Gtk.TreeViewColumn): GtkTreeViewColumn * template def__init__(self, gtk_tree_view_column_new_with_attributes ( TreeViewColumn( title=’’, constgchar *title, constGlib::ustring& title, cell_renderer=None, GtkCellRenderer *cell, constTreeModelColumn& column); **attributes): ...)G_GNUC_NULL_TERMINATED;
Kódrészlet 11.4. GtkTreeViewColumn létrehozása Ahogy látszik GtkTreeViewColumn létrehozható akár paraméterek megadása nélkül is, bár ennek csak akkor nincs sok értelme, hiszen a létrehozást követ˝oen úgy is megadjuk a paramétereket, amikre a létrehozáskor is lehet˝oségünk lett volna. Az oszlop kapcsán kézenfekv˝o paraméter az oszlop fejlécének szövege (title), a cella kirajzolását végz˝o objektum (renderer). Ezt a rajzolást végz˝o objektum paraméterei követik, amik közül messze a legfontosabb, hogy a modell melyik oszlopában található az adat, aminek a képerny˝on való megjelenítése a feladat lesz. Az egyes nyelvi változatokban léteznek függvények (insert_column, insert_column_with_attributes) az iménti példában látott paramétersorrenddel, amik egyrészt elkészítik a GtkTreeViewColumn, illetve GtkCellRenderer objektumokat, majd az oszlopot hozzá is adják nézethez, az els˝o paraméterként megadott pozícióra. Az oszlopok sorrendje tehát a hozzáadásakor már meghatározottá válik, hiszen az egymás után a nézethez hozzáadott (append_column) oszlopok – hasonlóan a konténerekbe helyezett widgetekhez – egymás mellett, balról jobbra helyezkednek majd el, a beszúrt oszlopoknál (insert_column) a helyet egy kötelez˝oen megadandó paraméter határozza meg. renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attribute ( GTK_TREE_VIEW (view), -1, "ID", renderer, "text", COL_ID, NULL); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes ( GTK_TREE_VIEW (view), -1, "Name", renderer, "text", COL_NAME, NULL);
view.append_column(
renderer = Gtk.CellRendererText() column = Gtk.TreeViewColumn(
"ID",
"ID", renderer, text=Model.COL_ID)
Columns.col_id);
view.append_column(
tree.append_column(column) renderer = Gtk.CellRendererText() column = Gtk.TreeViewColumn(
"Name",
"Name", renderer, text=Model.COL_NAME)
Columns.col_name); tree.append_column(column)
Kódrészlet 11.5. Példa GtkTreeView létrehozására Az oszlopok hozzáadásának két fontos kérdését segít tisztázni a fenti példa. Az els˝o az oszlop pozíciója. Mint arról szó esett az append_column függvény minden eseteben az eddigi oszlopok után f˝uzi az új oszlopot, ahogy ez a nevéb˝ol is következik, míg az insert_column a megadott pozícióra szúrja be azt, kivéve ha a pozíció −1, mert ez esetben az append_column függvénnyel azonos módon m˝uködik. Visszatérési értékük az új oszlop pozíciója ami alapján kés˝obb a nézet vissza tudja adni nekünk magát az oszlopot. Elkerülend˝o, hogy ez érték kompromittálódjon, célszer˝u az oszlopokat mindig a sor végére hozzáf˝uzni. A másik kérdés, hogy miként szerez tudomást a GtkCellRenderer objektum, hogy honnan szerzend˝o be a megjelenítend˝o adat, illetve mik lennének annak megjelenítési paraméterei. Az append_column, illetve az insert_column függvényeknek átadott utolsó két paraméter nem az oszlopnak, hanem a GtkCellRenderer objektumnak szóló paraméterek és azt mondják meg, hogy a létrejött GtkCellRendererText objektum az egyes sorok esetén az adattárló mely 76
oszlopából vegye a saját text paraméterének értékét. Nem meglep˝o módon a GtkCellRendererText objektum a text attribútumának értékét jeleníti meg a sor és az oszlop által meghatározott cellában. Látható, hogy számok megjelenítése épp ugyanezt a metódust használjuk ami azért lehetséges, mert a modellb˝ol egy általános adattárolóba (GValue) olvassuk ki a cella értékét, amit aztán karakterlánc formára konvertálva veszünk ki ebb˝ol a tárolóból. Amennyiben az átalakítás sikeres, a GtkCellRendererText is használható. A modell egy cellájában tárolt érték alapján történ˝o megjelenítés nem csak azt teszi lehet˝ové, hogy a megjelenítend˝o szöveget adjuk meg a modell valamelyik oszlopában – ami egyrészr˝ol értelemszer˝u, másrészr˝ol a m˝uködéshez elégedetlen is – de például annak el˝otét-, vagy háttérszínét (foreground, background) is. Különböz˝o adattípusok más-más megjelenítést igényelnek, ami egyúttal más GtkCellRenderer osztály is jelent. Képek megjelenítésére a GtkCellRendererPixbuf osztály illetve annak pixbuf tulajdonsága, gbooleann értékek jelöl˝onégyzetszer˝u megjelenítésére a GtkCellRendererToggle, illetve annak active tulajdonsága használható. Amennyiben folyamatindikátort jelenítenénk meg a cellában, annak értékét a value, szövegét pedig text tulajdonság kell tartalmazza. Ezen GtkCellRenderer osztályok, illetve az említett paramétereik épp így adhatóak meg, mint ahogy a GtkCellRenderer és annak text attribútuma a fenti példában.
11.2.2.
Kezelés
Modell Hozzáadás. Az adattároló feltöltésének els˝o lépése az új sor hozzáadása. Ennek legegyszer˝ubb módja az, ha az eddigi elemek mögé egy új elemet szúrunk be. Ez minden további nélkül megtehet˝o az adattároló (store)1 bejárót visszaadó append függvényével. Természetesen éppúgy lehet˝oség van a meglév˝o elemek elé (prepend), vagy adott pontra történ˝o beszúrására (insert), bár ezek gyakorlati jelent˝osége kisebb. Fák esetén ugyanezen függvények ugyanúgy léteznek, egy eltéréssel. Ez pedig az, hogy a els˝o paraméterként a szül˝o elemhez tartozó bejárót kell megadni, illetve ha legfels˝o szint˝u elemet szeretnénk, akkor a nyelvi változatnak megfelel˝o null értéket kell átadni. void gtk_list_store_append (GtkListStore *list_store, GtkTreeIter *iter);
iteratorappend();
defappend(self, row=None):
void gtk_list_store_insert_before (GtkListStore *list_store, GtkTreeIter *iter, GtkTreeIter *sibling);
iteratorinsert(
definsert_before(self, sibling, row=None):
constiterator& sibling);
Kódrészlet 11.6. Elem hozzáadása listához void gtk_list_store_append (GtkListStore *list_store, GtkTreeIter *iter, GtkTreeIter *parent); void gtk_list_store_insert_before (GtkListStore *list_store, GtkTreeIter *iter, GtkTreeIter *parent, GtkTreeIter *sibling);
iteratorappend( constTreeNodeChildren& parent); iteratorinsert( constiterator& sibling);
defappend(self, parent, row=None): definsert_before(self, parent, sibling, row=None):
Kódrészlet 11.7. Elem hozzáadása fához A különbség ez egyes nyelvek között talán nem látványos, de egy ponton mindenképpen számottev˝o. A C++, illetve Python nyelv˝u megvalósítás az új bejárót visszaadja, míg a C megoldásában egy kimeneti paraméterben kapjuk vissza. Ez els˝ore csak formai eltérésnek t˝unhet, de ennél azért mélyebb oka van. Az el˝obbi két nyelv esetén a bejárok objektumok, lévén az utóbbi nyelv esetében err˝ol nem lehet szó, mindössze egy struktúra. Az paraméterként elvárt mutató, mivel kimeneti paraméterr˝ol beszélünk, egy létez˝o bejáró struktúrára kell mutasson, amit az append, insert_before és egyéb függvények töltenek ki a megfelel˝o értékekkel, amit ezt követ˝oen vehetünk az adatok eléréséhez használatba. Feltöltés. Az elem hozzáadására használt függvények deklarációjából látszik, hogy a Python nyelv esetén lehet˝oség van egy lépésben hozzáadni egy új elemet az adattárolóhoz és egyszersmind megadni az új sor egyes oszlopokba es˝o értékeit, a row paraméter révén. A C illetve C++ változat ezt két lépésben végzi, el˝oször hozzáadja az új elemet az adattárolóhoz, majd csak ezt követ˝oen ad értéket a celláknak. Ez nem teljesen fedi a valóságot, mivel a C nyelv esetén is van mód az egy lépéses megoldásra, ami nem is meglep˝o, hiszen a Python változatban sem lenne különben erre lehet˝oség. A C nyelvi függvény neve insert_with_values, ami paraméterezését tekintve összef˝uzése az insert és a feltöltésre szolgáló set függvényekének. A C++ esetén a valóság megegyezik a látszattal, vagyis csak a két lépcs˝os megoldás létezik, illetve a helyzet még ett˝ol is egy kicsikét bonyolultabb. 1 megjegyzend˝ o,
hogy az elem hozzáadását nem a modell definiálja, ez csak olvasáshoz ad interfészt
77
classTreeRow : publicTreeIter { classTreeModel(Gtk.TreeModel): defset_row(self, treeiter, row):
void gtk_tree_store_set (GtkTreeStore *tree_store, GtkTreeIter *iter, ...); void gtk_tree_store_set_value (GtkTreeStore *tree_store, GtkTreeIter *iter, gint column, GValue *value);
template void set_value(
classTreeModelRow(object): defset_value(self, treeiter, column, value):
int column, constColumnType& data) const; template void set_value( constTreeModelColumn& column, constColumnType& data) const; template inlineTreeValueProxy operator[](constTreeModelColumn& column); def__setitem__(self, key, value): };
Kódrészlet 11.8. Sor értékeinek beállítása
Értékadás cellának. A értékadó függvények közös nevez˝oje – vagyis ami minden nyelvi változat esetén elérhet˝o – az a módszer, ami egyszerre csak az adott sor adott oszlopában található értéket állítja be (set_value). Az eltérés abban áll, hogy míg a GTK+ esetén az érték beállítását végz˝o függvény az adattárolóhoz köt˝odik, addig a C++ és Python esetén a sor reprezentálására szolgáló külön osztályok (TreeRow, TreeModelRow) keresztül valósul meg az érékadás. Ez persze leginkább formai különbségeket eredményez a három nyelv esetén a gyakorlati m˝uködés szempontjából nincs számottev˝o eltérés. A C változatban alkalmazott GValue osztály részleteit – mivel a gyakorlatban nem ez a módszer használatos – a kés˝obbiekre halasztjuk. Elöljáróban csak annyit, hogy a GValue egy olyan adattároló megoldás, ami képes különböz˝o típusú adatokat tartalmazni, míg erre a hagyományos típusok (int, double, . . . ) erre nem képesek. Felhasználásuk általában a generikus algoritmusokban történik, ahol csak futásid˝oben ismert az adott oszlop típusa. Mindhárom nyelv esetén az értéket beállító függvénynek paraméterként megadandó az oszlop sorszáma, illetve maga a beállítandó érték, pontosabban GTK+ esetén annak címe, míg gtkmm esetén annak referenciája. Ehhez képest az indexel˝o operátor használata csak annyiban más, hogy a sort reprezentáló objektum az adott oszlop számával indexelve a konkrét cellát érjük el, majd ennek adunk értéket. A gtkmm indexel˝o operátorra kétféleként is implementált, az egyik esetén numerikus adhatjuk meg az oszlop sorszámát, míg a másik esetben a modellt leíró osztály (ModelColumns) megfelel˝o adattagjának (col_id) segítségével, ahol az olvashatóság szempontjából mindenképpen ez utóbbi preferált. GValue id = G_VALUE_INIT; g_value_init(&id, G_TYPE_INT); g_value_set_int(&id, 42); gtk_list_store_set_value(store, &tree_iter, COL_ID, &id);
row.set_value(Columns.col_id, 42);
row.set_value(Model.COL_ID, 42)
row.set_value(0, 42); row[Columns.col_id] = 42;
row[Model.COL_ID] = 42
Kódrészlet 11.9. Példa sor egy értékének beállítására
Értékadás teljes sornak. Teljes sorok egyidej˝u kitöltésére is van lehet˝oség, azonban csak a C, valamint a Python változat esetén, ami komoly hiányossága a C++ változatnak. Ezen hiányosságok okozta nehézségekkel az egyszer˝ubb felhasználás esetén nem találkozunk, így ezzel csak kés˝obb foglalkozunk. A másik két nyelv jócskán eltér˝o módszerekkel éri el az egy lépéses beállítást és mindkét esetben lehet˝oség van részleges beállításra is. gtk_list_store_set (store, &tree_iter, COL_ID, 42, COL_NAME, "John Doe", -1);
store.set_row( tree_iter, [42, ’John Doe’]) store[tree_iter] = [42, ’John Doe’]
Kódrészlet 11.10. Példa teljes sor beállítására A GTK+ egy változó paraméterhosszúságú paraméterlistájú függvénnyel oldja meg a problémát, ahol a paraméterekben az oszlop sorszámok és a beállítandó értékek párjai követik egymást, amit egy −1 érték zár. Erre azért van szükség, hogy a set függvény paramétereinek kiolvasásakor tudja, mikor fejeztük be a az oszlopsorszám-érték párok felsorolást. Erre a negatív érték kiválóan alkalmas, hiszen az oszlopok sorszámozása nullától kezd˝odik, vagyis negatív értéket nem vehet fel. Egyébiránt a záróérték elmaradása kellemetlen meglepéseket eredményez, mivel a set függvény az oszlopsorszám helyén kapott negatív értékig olvassa a vermet, így erre érdemes különös figyelmet fordítani. A Python változat ett˝ol jóval kevésbé körülményes megoldást alkalmaz. A set_row függvénynek paraméterként, az indexel˝o operátor révén visszakapott TreeModelRow objektumnak pedig értékül adhatjuk a sor eleminek listáját, ahol a
78
sorrendnek követnie kell az adattároló létrehozásakor megadottakat, míg a C változatban szabadon választhatjuk meg a sorrendet, hiszen az oszlopok sorszámaikkal azonosítottak. Lekérdezés. A feltöltés kapcsán már tettünk említést a lekérdezésr˝ol, hiszen az objektum-orientált megoldások sorokat reprezentáló osztályai (TreeRow, TreeModelRow) nemcsak íráshoz, hanem olvasáshoz is ad interfészt. Lényegében tehát ezen esetek nem szorulnak különösebb magyarázatra, viszont a C nyelv sajátosságai, illetve az egyes nyelvi változatok kuszaságai miatt mégis érdemes kis kitér˝ot tenni. void gtk_tree_model_get (GtkTreeModel *tree_model, GtkTreeIter *iter, ...); void gtk_tree_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter, gint column, GValue *value);
classTreeRow : publicTreeIter {
classTreeModel(Gtk.TreeModel): def get(self, treeiter, *columns):
template ColumnType get_value(constTreeModelColumn& column) const; template inlineTreeValueProxy classTreeModelRow(object): operator[](constTreeModelColumn& column) const; def__getitem__(self, key): };
Kódrészlet 11.11. Sor értékeinek lekérdezése Cella lekérdezése. Minden nyelvi változat ad lehet˝oséget az egyes cellák értékének lekérdezésére. A C változat egyrészr˝ol az imént megismert GValue típus révén (get_value, másrészt egy változó hosszúságú paraméterlistájú függvény révén, amit a teljes sorok lekérdezésénél részletezünk. A másik két változat a már említett indexel˝o operátort használja az elemek lekérdezésére, aminek csak formai változata gtkmm get_value függvénye. Teljes sor lekérdezése. A GTK+ – a set függvényhez hasonlóan – oszlopsorszám és értékpárokat sorozatát várja paraméterként, amit szintén egy −1 értéknek kell zárni. A különbség a két függvény között abból adódik, hogy itt az értékeket ki szeretnénk nyerni az adattárolóból, tehát a tároló adott oszlopának típusával azonos típusú változó címét kell átadnunk az oszlopsorszámot követ˝oen, amibe az érték tárolásra kerül. Karakterláncok esetén (G_TYPE_STRING) – ami a leggyakoribb felhasználás – egy gchar * típusú változó címe adandó át a set függvénynek, amiben egy újólag allokált memóriaterület címét kapjuk vissza, amit a felhasználás végeztével fel kell szabadítanunk (g_free). Mutató típusú oszlop (G_TYPE_POINTER) esetén sem a tárolóba történ˝o behelyezés, sem az onnan történ˝o lekérdezéskor nem történik másolás – már csak azért sem mert a konkrét típusról, illetve annak másolásának módjáról a GTK+ mit sem tud –, a mutatott memóriaterület menedzsmentjér˝ol magunknak kell gondoskodnunk. A Python újfent egy nagyságrenddel egyszer˝ubb megoldást kínál, hiszen a get függvénynek átadhatjuk a lekérdezend˝o oszlopok listáját, ami a sor megfelel˝o oszlopaiban lév˝o értékek listájával tér vissza. Egyébiránt Python indexel˝o operátoránál megszokottak itt is érvényesülnek, vagyis akár intervallumokat is megadhatunk index gyanánt. Vezérl˝o A nézet vezérlése szempontjából számottev˝o jelent˝oséggel bír az elemek kiválasztása. Maga a nézet az általa megjelenített sorok kiválasztásával kapcsolatos teend˝oket egy külön osztályra bízza (GtkTreeSelection), amit a nézet létrejöttével egy id˝oben, automatikusan készít el, így ezzel nekünk nem kell tör˝odnünk. Egy adott nézethez tartozó objektum – a nevezéktannak megfelel˝oen – a get_selection függvénnyel kérdezhet˝o le. Módok. Az objektum viszonylag sz˝uk, de annál hasznosabb funkcionalitáshalmazzal rendelkezik. Ezek közül mindjárt az els˝o az elemek kiválasztásának módja (mode), ami meghatározza, hogy egyszerre hány elem lehet kiválasztva. A SELECTION_NONE érték azt jelenti, hogy egy elem sem, a SELECTION_SINGLE, hogy legfeljebb egy elem, a SELECTION_BROWSE, hogy egy és csak egy, a SELECTION_MULTIPLE pedig, hogy akárhány elem lehet kiválasztva egy id˝oben. Kiválasztott elemek kezelése. El˝obbiek komoly hatással vannak a GtkTreeSelection osztály másik funkciójára, a kiválasztott elemek kezelésére. Ez egyfel˝ol jelenti a kiválasztott elemek hozzáadását (select_iter, select_path, select_all) és elvételét (unselect_iter, unselect_path, unselect_all), illetve az aktuálisan kiválasztott elemek lekérdezésére. A kiválasztás esetén ha a mód szerint legfeljebb egy elem lehet kiválasztva, ha volt korábban kiválasztott elem az elveszti kiválasztott mivoltát és az új elem nyeri el ezt míg, ha mód szerint több elem kiválasztása is lehetséges, akkor a korábbiak mellet az új elem is kiválasztásra kerül. Ha egy elem kiválasztása sem engedélyezett, akkor a függvényhívás hatástalan. A kiválasztás megszüntetésénél a módtól függetlenül megsz˝unik az elem kiválasztott állapota.
79
kiválasztott elemek lekérdezése. Mindössze két függvény van, amivel a kijelölt elemek lekérdezhet˝oek. Az egyik (get_selected) csak abban az esetben m˝uködik, ha legfeljebb egy elemet lehet kiválasztani és eredményeként egy bejárót kapunk vissza, míg a másik get_selected_rows minden esetben helyes eredményre vezet, de felesleges bonyolítást jelent, különösen a C változat esetén, ahol visszatérési értékét – a kijelölt elemek útvonalait tartalmazó listát – természetesen fel is kell szabadítani.
11.2.3.
Szignálok
Vezérl˝o A kiválasztást vezérl˝o osztály mindössze egy, de annál nélkülözhetetlenebb szignált (changed) nyújt, ami akkor váltódik ki, amikor a kiválasztott elem mennyiségében változás áll be. A változást a rendszer csak egyszer jelzi, vagyis ha számos elem ki volt választva és meghívjuk az unselect_all függvényt, akkor mindössze egyszer váltódik ki a changed szignál, nem pedig annyiszor, ahány elem korábban kiválasztva volt. Természetesen ha egyesével (unselect_iter) csökkentjük a kiválasztott elemek számát, akkor ennek megfelel˝oen a szignál kiváltódására is többször kerül sor. Ne feledjük, ha a kijelölt elemeket akarjuk törölni, akkor az egyes elemek törlése érvénytelenítheti a többi elemet. Törölni tehát mindig a lentebb lév˝o elemeket kell el˝oször, mivel ez a fenti elemek útvonalát nem változtatja.
11.3.
Tesztelés
11.3.1.
Keresés
Els˝oként az fontos tisztázni, hogy a GtkTreeView által megjelenített adatokra – függetlenül attól, hogy a hozzátartozó modell GtkListStore vagy GtkTreeStore – az akadálymentesítési réteg, mint táblázatra tekint, ami kés˝obb számos m˝uködési sajátosságra lesz majd hatással. Els˝oként arra, hogy miként érjük el magát a GtkTreeView objektumnak megfelel˝o Node objektumot, az GtkTreeModel által elérhet˝o adatokat, illetve az oszlopok fejléceit. A nézetnek egy önálló Node felel meg, aminek roleName értéke table, vagy tree table, el˝obbi ha listáról, utóbbi ha fáról van szó. Ezen objektumnak két típusú gyereke van. Az egyik az egyes oszlopok fejléceit reprezentáló Node, amiknél a roleName érték table column header. Ezen gyerekek mindig a nézetnek megfelel˝o Node els˝o n elemét jelentik, ahol az n a nézet aktuálisan látható oszlopainak száma. Az ezeket követ˝o gyerekek a cellákat reprezentálják, roleName értékük table cell, számosságuk megfelel a látható oszlop és a sorok számának szorzatával. Az oszlopfejléceket és a cellákat reprezentáló Node objektumok nevei felveszik azt az értéket, amit szövegesen tartalmaznak, így megtalálásuk ennek megfelel˝oen történhet. Ezen objektumok rejtenek tehát minden információt, illetve rajtuk keresztül érhetünk minden olyan funkciót amire a nézet vezérléséhez szükség lesz.
11.3.2.
Interfészek
Nézet. A nézet Node objektuma implementálja az table interfészt, ami egyrészr˝ol a táblázat általános adataihoz enged hozzáférést, mint amilyen a sorok száma (nRows), az oszlopok száma (nColumns), az oszlop fejléce (columnHeader). Másrészr˝ol hozzáférhetünk az egyes cellákhoz x, illetve y pozíció alapján (getAccessibleAt). Cellák. Az egyes cellák – már amik a GtkCellRendererText segítségével rajzolódnak ki – implementálják a text interfészt ennek megfelel˝oen az általuk megjelenített értékek a korábbiakban megismerteknek megfelel˝oen olvashatóak ki. A GtkCellRendererPixbuf osztályt használó cellák az image interfészen keresztül érhet˝oek el, így biztosítva információkat a megjelenített képekr˝ol.
11.3.3.
Állapotok
A GtkCellRendererToggle cellák értékei a GtkCheckButton használatakor bemutatott pyatspi.STATE_TOGGLE státusz alapján kérdezhet˝oek le.
11.3.4.
Akciók
A GtkCellRendererToggle cellák értékei a GtkCheckButton kapcsán megismert toggle akció révén módosítható.
80
A. Függelék
Licencelési feltételek Ez a m˝u a Creative Commons Nevezd meg!-Így add tovább! licencének hatálya alatt áll. A következ˝oket teheted a muvel:. ˝ • szabadon másolhatod • terjesztheted • bemutathatod és el˝oadhatod a m˝uvet • származékos m˝uveket (feldolgozásokat) hozhatsz létre Az alábbi feltételekkel:. Nevezd meg! A szerz˝o vagy a jogosult által meghatározott módon fel kell tüntetned a m˝uhöz kapcsolódó információkat (pl. a szerz˝o nevét vagy álnevét, a M˝u címét). Így add tovább! Ha megváltoztatod, átalakítod, feldolgozod ezt a m˝uvet, az így létrejött alkotást csak a jelenlegivel megegyez˝o licenc alatt terjesztheted. Az alábbi figyelembevételével:. Elengedés A szerz˝oi jogok tulajdonosának engedélyével bármelyik fenti feltételt˝ol eltérhetsz. Más jogok A következ˝o jogokat a licenc semmiben nem befolyásolja: • A fentiek nem befolyásolják a szabad felhasználáshoz f˝uz˝od˝o, illetve az egyéb jogokat. • A szerz˝o személyhez f˝uz˝od˝o jogai • Más személyeknek a m˝uvet vagy a m˝u használatát érint˝o jogai, mint például a személyiségi jogok vagy az adatvédelmi jogok. Jelzés Bármilyen felhasználás vagy terjesztés esetén egyértelm˝uen jelezned kell mások felé ezen m˝u licencfeltételeit. Ez a Legal Code (jogi változat, vagyis a teljes licenc) szövegének közérthet˝o nyelven megfogalmazott kivonata, teljes változata a Creative Commons oldalán érhet˝o el.
81
Tárgymutató örökl˝odés, 7, 23, 25 átlátszatlan mutató, 7
dogtail.Node függvények satisfies, 42 tulajdonságok name, 42 roleName, 42 dogtail.Nodedogtail.Node függvények findChild, 42 dogtail.Predicate, 42 függvények satisfiedByNode, 42 dogtail.Rootdogtail.Root függvények application, 42 dogtail.tree függvények applications, 41 application, 41
ablak modalitás, 29 pozíció, 31 típus, 30 popup, 29, 30, 33 toplevel, 29, 30, 33 tranziencia, 30, 31 ablakkezel˝o, 19 bezárás, 19 minimalizálás, 19 Accerciser, 6 ATK, 6 AtkObject, 42, 59 függvények set_image_description, 59 set_name, 42 Atspi.Accessible, 59 Atspi.AccessibleAtspi.Accessible függvények getState, 43 Autoconf, 14 Automake, 14 automata tesztelés, 5 Autotools, 14
egységbezárás, 7 fókusz billenty˝uzet, 33 fordítás, 18, 27 fordítási idej˝u típusellen˝orzés, 8 futtatás, 18, 27 Gail, 6 GDK backend Broadway, 3 Wayland, 3 X11, 3 GIMP, 2 Git, 2 GLib függvények print, 22 makrók G_GNUC_PRINTF, 36 NULL, 23 OBJECT, 23 típusok gboolean, 23 GObject, 4 GObject, 7–9 függvények signal_connect_data, 23 unref, 75 makrók signal_connect, 23 signal_connect_swapped, 24
billenty˝u enter, 33 tab, 33 bináris csomag, 13 C, 5, 13 C++, 4, 13 Cairo, 4 callback, 9 deb, 13 dinamikus modulbetöltés, 4 Dogtail, 6, 13 procedural, 19 focus.application, 20 GtkDemoTest, 20 tree, 19 root.application, 20 utils run, 20 dogtail.Config tulajdonságok searchBackoffDuration, 41 searchCutoffCount, 41
82
tagfüggvények connect, 27 connect_object, 27 GObject Introspection, 5 gomb jóváhagyó, 40 menekül˝o, 40 grafikus szerver Wayland, 3 X11, 3 GTK, 13 GTK, 3 fejlécfájlok, 17 GDK, 3, 4 GLib, 3 GIO, 4 GModule, 4 GObject, 4 GThread, 4 kódolási konvenciók, 16 formázás, 16 nevezéktan, 16 Gtk függvények false, 37 init, 17 main, 18 main_quit, 24 true, 37 konstansok RESPONSE_DELETE_EVENT, 39 RESPONSE_NONE, 39 WIN_POS_ALWAYS, 31 WIN_POS_CENTER, 31 WIN_POS_CENTER_ON_PARENT, 31 WIN_POS_MOUSE, 31 WIN_POS_NONE, 31 tagfüggvények main_quit, 25, 27 GtkBin, 29, 32, 45 GtkBox, 29, 45 függvények pack_end, 29, 47 pack_start, 29, 47 gyerek tulajdonságok expand, 32, 46–49 fill, 32, 46–49 pack-type, 46, 49 padding, 47, 50, 51 tulajdonságok homogeneous, 47–49 spacing, 47, 50 GtkButton függvények new_with_label, 23 szignálok clicked, 23–25, 27 destroy, 27 GtkButtonBox, 29 GtkComboBoxText függvények
append_all, 69 get_active_text, 70 insert_all, 69 new, 69 new_with_entry, 69 prepend_all, 69 remove, 70 remove_all, 70 GtkContainer, 33, 45 függvények add, 47 add, 48 remove, 48 remove, 48 set_focus_chain, 33 GtkDialog, 29, 30, 34 bels˝o elemek action_area, 29 content_area, 29 függvények run, 31, 36, 39 set_alternative_button_order, 40 set_alternative_button_order_from_array, 40 szignálok response, 39 GtkEditable, 61 függvények set_editable, 62 GtkEntry, 33 függvények get_text, 62 new, 61 new_with_buffer, 61 set_invisible_char, 62 set_placeholder_text, 63 set_text, 62 set_visible, 62 tulajdonságok activates-default, 33 caps-lock-warning, 63 invisible-char, 62 placeholder-text, 63 progress-fraction, 63 progress-pulse, 63 progress-pulse-step, 63 text, 62 visibility, 62 GtkEntryBuffer, 61, 63 GtkGrid, 45 függvények attach, 48 tulajdonságok column-homogeneous, 46, 48, 50 column-spacing, 46, 50 row-homogeneous, 46, 48, 50 row-spacing, 46, 50 GtkImage, 53 GtkLabel, 53 függvények new, 55 new_with_mnemonic, 55 83
GtkMessageDialog, 35 GtkMisc tulajdonságok xalign, 55 xpad, 55 yalign, 55 ypad, 55 gtkmm, 4, 7 GtkRadioButton, 70 GtkSeparator, 29 GtkSpinButton, 64 szignálok input, 64 output, 64 tulajdonságok value, 62 GtkStockId, 34 GtkTextView, 33 GtkToggleButton szignálok toggled, 70 tulajdonságok active, 70 inconsistent, 70 GtkTooltip, 54 GtkWidget, 8 függvények destroy, 23–25, 47 get_accessible, 42 grab_focus, 33 hide, 25, 31, 38 show, 18, 31, 36, 39 show_all, 36 szignálok delete-event, 23, 25–27, 38, 39 destroy, 24, 39 unmap, 39 tulajdonságok can-default, 33 can-focus, 33 expand, 48 halign, 48, 49 has-default, 33 hexpand, 48 valign, 48, 49 vexpand, 48 GtkWindow, 29, 30 függvények fullscreen, 40 get_position, 31 iconify, 40 maximize, 40 move, 31 new, 18, 23 present, 36 resize, 32 set_default_size, 31 set_deletable, 40 set_geometry_hints, 32 set_keep_above, 40 set_position, 31
set_resizable, 32 set_skip_taskbar_hint, 40 set_transient_for, 31 stick, 40 tulajdonságok destroy-with-parent, 30 gravity, 31 modal, 29, 30, 36 size-request, 31 transient-for, 30 type, 29 GUI, 3 GUI eszközkészlet, 2 interfész, 10 konténer, 45 size allocation, 49 size request, 31 ”lebeg˝o” referencia, 10, 18, 47, 75 libsigc++, 4 libsigc++ függvények bind, 25 mem_fun, 25 ptr_fun, 25 típusok slot, 25 Libtool, 14 licenc LGPL, 2 Linux, 2, 3 Mac OS X, 2, 3 Main, 17 függvények quit, 18 run, 18 main loop, 9, 21, 36, 39 Motif, 2 nyelv változatok gtkmm, 13 PyGObject, 13 nyelvi változatok GTK+, 13 operációs rendszer Linux, 2, 3 Mac OS X, 2, 3, 9 Windows, 2, 3, 9 Pango, 4 POSIX, 4 PyGObject, 5, 7 Python, 5, 13 Python os kill, 20 signal SIGTERM, 20 84
time sleep, 20 unittest, 19 main, 20 setUp, 20 tearDown, 20 TestCase, 20 referencia-számlálás, 10, 48, 75 rpm, 13 SignalProxyBase tagfüggvények connect, 25 szül˝o-gyerek kapcsolat, 10 szálkezelés, 4 szignál, 21, 23 alapértelmezett eseménykezel˝o függvény, 23, 26 blokkolása, 23, 26, 27 eseménykezel˝o függvények sorrendje, 24 eseménykezel˝o felkötése, 23, 25, 27 típuskényszerítés, 23, 24 teszt szkript futtatás, 20 widget, 8 szignálok, 8 delete-event, 18, 19 tulajdonságok, 8 Windows, 2, 3 wrapper, 4
85
Táblázatok jegyzéke 1.1
A GTK+, gtkmm, PyGObject összehasonlítása . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
86
5
Ábrák jegyzéke 2.1 2.2
Örökl˝odés hasonló funkciójú widgetek között . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Akadálymentesített szoftverek elérése[1] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8 11
4.1
Minimális mintapéldák képerny˝oképi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19
6.1 6.2 6.3 6.4 6.5
Billenty˝uzet fókusz jelzése a widgeten[6] . . . Gombok szokások sorrendje egy dialógusban[6] Tipikus ablakszerkezetek[6] . . . . . . . . . . Egy tipikus gombsor . . . . . . . . . . . . . . Tipikus üzenetablakok . . . . . . . . . . . . .
. . . . .
33 33 34 34 35
7.1 7.2
Méretarányos és homogén elhelyezésének a konténeren belül[8] . . . . . . . . . . . . . . . . . . . . . . Tér az elemek között és körül a konténerben[8] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
49 50
8.1 8.2 8.3
Címke[9] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kép[9] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Buborék[7] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
53 54 54
9.1 9.2 9.3
Szöveg és szám bevitele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ikonok használata szövegbeviteli mez˝oben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ikonok használata szövegbeviteli mez˝oben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
60 63 63
10.1 10.2 10.3 10.4
Jelöl˝onégyzet[9] . . . . . . . . . . . . . . . . . Rádiógombok[9] . . . . . . . . . . . . . . . . ComboBox[9] . . . . . . . . . . . . . . . . . . Inkonzisztens állapot jelöl˝onégyzetek esetén[6]
. . . .
67 68 68 68
11.1 Útvonal fa szerkezeten belül[6] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
74
. . . . .
. . . .
. . . . .
. . . .
87
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
Kódrészletek jegyzéke 4.1 Minimálisan szükséges kód GTK+, gtkmm, illetve PyGobject használata mellett . . . . . . . . 4.2 Minimálisan szükséges teszt tree, illetve procedural API használata mellett . . . . . . . . . . 5.1 Szignálok kezelése C, illetve C++ nyelven . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Szignálok kezelése Python nyelven . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1 Window létrehozása . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Dialog létrehozása . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.3 Minimál példa GtkWindowhoz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.4 Gombok hozzáadása GtkDialoghoz[6] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.5 Tipikus gombsor hozzáadása GtkDialoghoz[6] . . . . . . . . . . . . . . . . . . . . . . . . . 6.6 MessageDialog létrehozása . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.7 Szignálkezel˝o függvény perzisztens ablakhoz . . . . . . . . . . . . . . . . . . . . . . . . . . 6.8 Szignálkezel˝o függvény bekötése perzisztens ablakhoz . . . . . . . . . . . . . . . . . . . . . 6.9 Egyszer˝usített szignálkezel˝o függvény bekötése . . . . . . . . . . . . . . . . . . . . . . . . . 6.10 Egyszer˝usített szignálkezel˝o függvény bekötése . . . . . . . . . . . . . . . . . . . . . . . . . 6.11 Minimál példa GtkDialoghoz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.12 Minimál példa GtkDialoghoz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.13 Egyszer˝usített response-kezel˝o függvény bekötése . . . . . . . . . . . . . . . . . . . . . . . . 6.14 Eseménykezel˝o felüldefiniálása saját widgetosztályban . . . . . . . . . . . . . . . . . . . . . 6.15 Alternatív gombsorrend beállítása . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.16 Alapvet˝o elemek keresése teszt tree, illetve procedural API használata mellett . . . . . . . . . 6.17 Node keresése GenericPredicate, illetve findChild függvény segítségével . . . . . . . . . 6.18 Applikáció keresése application függvénnyel, illetve IsAnApplicationNamed objektummal 6.19 Ablak keresése specializált Predicate objektummal . . . . . . . . . . . . . . . . . . . . . . 6.20 Node státuszvizsgálatához szükséges függvény sémája . . . . . . . . . . . . . . . . . . . . . 8.1 Címke létrehozása . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.1 Az input és output szignálok felüldefiniálása az alapértelmezett m˝uködést implementálva . . 9.2 Az input és output szignálok felüldefiniálása hónapok megjelenítéséhez . . . . . . . . . . . 10.1 CheckButton létrehozása . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2 ComboBoxText létrehozása . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.1 GtkListStore létrehozása . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2 Példa GtkListStore létrehozására . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.3 GtkTreeView létrehozása . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.4 GtkTreeViewColumn létrehozása . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.5 Példa GtkTreeView létrehozására . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.6 Elem hozzáadása listához . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.7 Elem hozzáadása fához . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.8 Sor értékeinek beállítása . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.9 Példa sor egy értékének beállítására . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.10Példa teljes sor beállítására . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.11Sor értékeinek lekérdezése . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
88
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17 19 22 26 30 31 32 34 35 36 37 37 37 38 39 39 39 40 41 41 42 42 43 43 55 64 65 69 69 75 75 76 76 76 77 77 78 78 78 79
Irodalomjegyzék [1] GNOME Accessibility Developers Guide. stable/.
http://developer.gnome.org/accessibility-devel-guide/
[2] GNU Coding Standards. http://www.gnu.org/prep/standards/standards.html. [3] Linux kernel coding style. http://www.kernel.org/doc/Documentation/CodingStyle. [4] The Python Standard Library. http://docs.python.org/py3k/library. [5] FSF.hu Alapítvány. Szabad szabad-szoftver-palyazat-2011.
szoftver
pályázat
2011.
http://fsf.hu/2011/09/27/
[6] Calum Benson, Adam Elman, Seth Nickell, and Colin z Robertson. GNOME Human Interface Guidelines. http: //library.gnome.org/devel/hig-book/. [7] Murray Cumming et al. Programming with gtkmm 3. The GNOME Project. http://developer.gnome.org/ gtkmm-tutorial/unstable/. [8] Tony Gale, Ian Main, and the GTK team. GTK+ 2.0 Tutorial. The GNOME Project. http://library.gnome. org/devel/gtk-tutorial/stable/. [9] The GNOME Project. GTK+ 3 Reference Manual. http://developer.gnome.org/gtk3/stable/. [10] Havoc Pennington. GTK+ / Gnome Application Development. New Riders Publishing, 1999. http://developer. gnome.org/doc/GGAD/ggad.html. [11] Read the Docs project. The Python GTK+ 3 Tutorial. http://python-gtk-3-tutorial.readthedocs.org.
89