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.