C dla każdego (cz. 15.)
Gadżety (3)
To już trzecia część o gadżetach, a końca nie
widać. Mamy jednak nadzieję trochę dziś
"podgonić".
Zacznijmy od omówienia fragmentu listingu 13,
przedstawionego w poprzedniej części.
Przede wszystkim zdefiniowaliśmy sobie stałe,
będące identyfikatorami wszystkich użytych
gadżetów. Zauważcie, że są one równocześnie
indeksami elementów w tablicach.
Tablice, w których nic nie będziemy zmieniać,
definiujemy z atrybutami "const static". Pozwala
to kompilatorowi na bardziej efektywne
rozplanowanie elementów w pamięci (mogą zostać
umieszczone razem z kodem programu). Zmienne tak
zdefiniowane polepszają ponadto przejrzystość
programu, jako że ewentualne próby ich modyfikacji
są zgłaszane przez kompilator jako błędy, więc gdy
się widzi takie zmienne, to wiadomo, że są one
przeznaczone tylko do odczytu. Są to więc
właściwie stałe, tyle że w języku C nie są one w
pełni obsługiwane (nie są uznawane za stałe
wyrażenie) -- w pełni potrafi je wykorzystać język
C++.
Zmienna "verstr" to tzw. version string. Jej
format jest dokładnie zdefiniowany. Zaczyna się od
sekwencji "$VER:", po której następuje spacja i
nazwa programu, po niej z kolei wersja programu i
w nawiasie data w formacie "dd.mm.yy". Ten
"version string" jest potrzebny komendzie
"Version", która pozwala użytkownikowi uzyskać
informację o wersji programu. Istnieją programy
automatyzujące zarządzanie tego typu zmiennymi,
np. zwiększające numer wersji przy każdej
kompilacji, ustawiające właściwie datę itp., są to
np. "BumpRev", "HWGRCS". Kompilator SAS/C od
wersji 6.55 ma makro preprocesora "__AMIGADATE__",
zawierające datę kompilacji w wymaganym przez
komendę "Version" formacie.
Definiujemy więc tablicę struktur "NewGadget":
"tabnewgad", tablicę typów gadżetów: "tabgadtypes"
i tablicę tagów: "tabgadtags". Definiujemy też
pomocnicze tablice "cyclegadlab" i "mxgadlab" z
napisami opisującymi poszczególne pozycje w
gadżetach: "kręciołku" i "przyciskach radiowych",
a także listę pozycji "lvlista" dla gadżetu-listy.
Jak nietrudno zauważyć, nie otwieramy i nie
zamykamy bibliotek, nie sprawdzamy też wersji
systemu operacyjnego. Dlaczego? Po prostu szkoda
nam na to miejsca. Wydaje nam się, że nasi stali
Czytelnicy są już na takim poziomie, że nie zrobi
im to różnicy -- sami będą w stanie sobie te parę
linii dopisać. Zresztą w wypadku większości
współczesnych kompilatorów obsługi bibliotek w
ogóle nie trzeba będzie dopisywać: kompilatory
umożliwiają automatyczne otwieranie/zamykanie
użytych przez program bibliotek, korzystając z
tzw. mechanizmów autoinicjalizacji/autoterminacji.
Definiujemy również funkcję "wyjscie()", która
zapewnia nam eleganckie wyjście z programu w
każdej sytuacji -- zwalnia ona pamięć, zamyka okno
itd.
Omówienie przedstawionego miesiąc temu fragmentu
mamy więc za sobą. Przyjrzyjcie się teraz
dzisiejszemu fragmentowi.
Zacznijmy od funkcji "stworzgadzety()".
Tworzymy w niej zaczątek listy gadżetów z użyciem
omówionej wcześniej funkcji CreateContext(), a
następnie, w pętli, wszystkie gadżety.
Strukturę "NewGadget" opisująca każdy z gadżetów
kopiujemy do zmiennej lokalnej, gdyż będziemy w
niej dokonywać modyfikacji. Moglibyśmy właściwie
dokonywać modyfikacji bezpośrednio w tablicy,
gdybyśmy jej nie zdefiniowali jako "const".
Miałoby to jednak pewne wady, gdyby gadżety miało
się tworzyć kilka razy (np. przy każdym otwarciu
okna). Trzeba by zapamiętać w jakiejś pomocniczej
zmiennej, że modyfikacji już raz się dokonało i
nie należy robić ich ponownie. Tak więc po
skopiowaniu elementu tablicy skalujemy i
przesuwamy jego współrzędne i rozmiar. W pole
"ng_VisualInfo" wstawiamy wartość zwróconą przez
funkcję GetVisualInfoA(), w pole "ng_TextAttr"
adres zmiennej "czcionka", opisującej czcionkę, a
w pole "ng_UserData" adres elementu tablicy
"tabobgad", typu "struct ObslugaGadzetu", który to
typ omówimy w kolejnych częściach.
Zarówno "czcionka", jak i "tabobgad" mają zasięg
globalny, więc ich adres jest wielkością stałą.
Można by więc zapytać, dlaczego nie wstawiliśmy
ich adresów bezpośrednio w definicji tablicy
"tabnewgad"? Mogliśmy tak zrobić, ale... program
po skompilowaniu byłby dłuższy. Jak wiadomo,
programy na Amidze są relokowalne, a więc nie
muszą się znajdować w jednym, określonym podczas
kompilacji, obszarze pamięci. To powoduje, że
każde pobranie adresu dla celów inicjalizacji
zmiennych statycznych musi być odnotowane w tzw.
tablicy relokacji, a to zajmuje 8 bajtów. Dlatego
też w naszym wypadku krócej będzie zrobię to już w
kodzie programu, raz -- w pętli. Całkowity zysk
wyniósł -- w wypadku kompilatora SAS/C 6.56 -- 72
bajty, a GNU CC 2.7.2 -- 68 bajtów.
Kolejnym krokiem jest skorygowanie tablicy tagów
dla niektórych gadżetów: wstawiamy wartości znane
dopiero podczas wykonywania programu.
Dla gadżetu-listy wstawiamy jako daną tagu
"GTLV_ShowSelected" adres stworzonego w poprzednim
obrocie pętli string-gadżetu.
Dla gadżetu-palety wstawiamy jako daną tagu
"GTPA_Depth" liczbę bitplanów na ekranie, na
którym otworzymy okno. Informację tę pobieramy z
pola "Depth" struktury "BitMap" naszego ekranu.
Zauważcie, że do struktury "BitMap" dostajemy się
przez "RastPort", który zawiera w polu "BitMap"
wskaźnik na nią, zamiast bezpośrednio przez
"Screen", który ma tą strukturę "wbudowaną" w polu
"BitMap". Takie jest po prostu zalecenie autorów
systemu: w przyszłości BitMap "wbudowany" w
strukturę "Screen" przestanie być ważny, gdyż
stanie się zbyt mały (projektanci w roku 1985 nie
przewidzieli, że w niecałe 10 lat później ktoś
będzie chciał używać więcej niż 8 bitplanów, a
więc 256 kolorów, a tylko na tyle pozwala obecna
struktura "BitMap").
To, co robimy dla gadżetu-suwaka, opiszemy z braku
miejsca za miesiąc.
Po ewentualnym skorygowaniu tablicy tagów program
wreszcie tworzy dany gadżet, korzystając z
omówionej wcześniej funkcji CreateGadgetA(). Jej
rezultat zapamiętujemy w zmiennej "gad" dla
kolejnego obrotu pętli oraz w globalnej tablicy
wskaźników na gadżety "tabgad".
W pętli używamy dwóch zmiennych indeksujących.
Główna -- "licznik" -- indeksuje tablice
"tabnewgad", "tabgadtypes" i "tabgad". Tablica
tagów jest jednak zdefiniowana w sposób odmienny:
są w niej tagi dla wszystkich gadżetów "na kupie"
-- jeden po drugim. Dlatego też niezbędna jest
osobna zmienna indeksująca "licztag", zajmująca
się właśnie tablicą tagów. Koniec tagów dla
właśnie stworzonego gadżetu rozpoznajemy po tagu
"TAG_DONE", natychmiast po nim zaczynają się tagi
kolejnego gadżetu.
Tak więc funkcję "stworzgadzety()" mamy mniej
więcej za sobą. Przyjrzyjmy się teraz liniom
dodanym do funkcji main(). Widzimy tam jakieś
matactwa ze zmienną "zoom" typu "struct IBox",
który to jest zdefiniowany w
"intuition/intuition.h":
struct IBox
{
WORD Left;
WORD Top;
WORD Width;
WORD Height;
};
Zmienna "zoom" jest nam potrzebna przy otwieraniu
okna dla tagu "WA_Zoom". Tag ten powoduje dodanie
na górnej belce okna gadżetu skokowej zmiany
wielkości. Zmienna "zoom", której adres należy
podać jako daną tagu, zawiera informacje o
alternatywnym rozmiarze okna. "Height" ustawiamy
więc na wysokość górnej belki, "Width" na taką
szerokość, aby zmieścił się cały tytuł okna i
gadżety. Wartości 79 i 53 są dobrane
doświadczalnie. Jest to suma szerokości gadżetu
zamykania, gadżetu przód/tył i gadżetu skokowej
zmiany wielkości. W zależności od rozdzielczości
ekranu system używa różnych rozmiarów gadżetów --
rozpoznajemy to po tym, czy jest ustawiona flaga
"SCREENHIRES" w polu "Flags" struktury "Screen".
Jeżeli program zostanie uruchomiony pod OS 3.0+,
ustawiamy "Left" i "Top" na -1. Oznacza to, że
chcemy mieć tzw. size-only zoom, tzn. że po
kliknięciu na gadżet skokowej zmiany wielkości
zmieniać ma się tylko szerokość i wysokość okna.
OS 2.0x nie ma takiej możliwości, więc pod nim
ustawiamy "Left" i "Top" na początkowe współrzędne
okna.
Następnie otwieramy okno, używając do tego trzech
nie omówionych jeszcze tagów:
WA_InnerWidth, WA_InnerHeight -- daną jest
szerokość/wysokość wnętrza okna, tzn. należy podać
wartości bez ramek.
WA_Gadgets -- daną jest wskaźnik na pierwszy
element listy gadżetów, w naszym wypadku na
"zaczątek" stworzony przez funkcję
CreateContext().
Po otwarciu okna niezbędne jest narysowanie
znajdujących się w nim gadżetów. Posłużymy się
funkcją:
void GT_RefreshWindow( struct Window *win, struct
Requester *req );
Jako "win" należy podać adres okna, zawierającego
gadżety, parametr "req" jest nie wykorzystywany i
należy podać po prostu 0.
Dokończenie opisu zaprezentowanego dziś fragmentu
listingu przedstawimy za miesiąc. Pokażemy
również, jak obsługiwać gadżety.