C dla każdego (cz. 2.)
Okno na szary świat
Najwyższy czas zabrać się do konkretnej pracy.
Dziś zajmiemy się oknami, obracać się więc
będziemy w kręgu biblioteki Intuition, która
zarządza GUI.
Zacznijmy od, dostępnych dopiero po otwarciu
biblioteki Intuition, funkcji do otwierania okien.
Dawniej istniała tylko jedna taka funkcja:
struct Window *OpenWindow( struct NewWindow
*newWindow );
Obecnie (od systemu 2.0) istnieje też druga:
struct Window *OpenWindowTagList( struct NewWindow
*newWindow, struct TagItem *tagList );
struct Window *OpenWindowTags( struct NewWindow
*newWindow, unsigned long tag1Type, ... );
Pewnie cisną się Wam na usta dwa pytania: co robią
powyżej dwa prototypy funkcji, skoro miał być
tylko jeden, i co oznaczają parametry tych
funkcji. Pierwszy parametr to wskaźnik na
zainicjowaną wcześniej strukturę informacyjną, w
której umieszczało się informacje o tym, jaki miał
być rozmiar okna, jakie miało mieć ono gadżety,
tytuł itp. Piszemy w czasie przeszłym, ponieważ
parametru tego w dzisiejszych czasach już się
właściwie nie używa (jako pierwszy parametr podaje
się po prostu NULL, czyli 0). Struktura NewWindow
została wyparta przez tagi.
Dlaczego tagi?
System się rozwija. Od czasu pierwszej Amigi dużo
zostało zrobione. Niestety, nawet pomimo dużej
liczby "furtek", pozostawionych przez twórców
systemu, niektóre struktury stały się w końcu zbyt
ciasne -- klasycznym przykładem jest właśnie
struktura NewWindow. Zastępowanie ich w każdym
systemie nowymi nie ma sensu. Właśnie dlatego
pojawiły się tagi. Za ich pomocą można przekazywać
do funkcji nieograniczone ilości informacji, bez
posługiwania się sztywnymi strukturami, tagi
umożliwiają też bezproblemowe dodawanie nowych
elementów do systemu operacyjnego. Argumenty
podaje się parami (strukturami TagItem) --
pierwszy element pary to właśnie TAG, który jest
po prostu stałą informującą, co oznacza dana
przekazana w drugim elemencie pary, może to być
np. szerokość okna, jego tytuł (wskaźnik na nazwę)
itd. Po pierwszej parze może nastąpić druga,
trzecia i następne.
Funkcje korzystające z tagów mogą być wywołane na
dwa sposoby. Istnieją dwie deklaracje tej samej
funkcji bibliotecznej (różniące się zwykle
końcówką nazwy -- tak jak w wypadku
OpenWindowTagList() i OpenWindowTags()). Pierwszym
ze sposobów wywołania takiej funkcji jest podanie
jej wskaźnika na tablicę tagów w charakterze
argumentu (w naszym wypadku chodzi o ostatni
argument funkcji OpenWindowTagList()). Tablica
taka jest wypełniona strukturami TagItem. W drugim
sposobie (OpenWindowTags()) zamiast wskaźnika na
zdefiniowaną wcześniej tablicę podaje się po
prostu pary jako kolejne parametry do funkcji o
zmiennej liczbie argumentów (na podobnej zasadzie,
jak w "printf()"). Oba sposoby są równoważne, w
rzeczywistości funkcja przyjmuje argumenty w tej
pierwszej postaci, a druga jest po prostu
"tłumaczona", przez kompilator, na pierwszą, a
istnieje dlatego, że jest wygodniejsza w użyciu.
Warto wspomnieć o tagu, który MUSI się pojawić na
końcu, w tablicy struktur TagItem, lub jako
ostatni argument funkcji. Jest nim TAG_END,
nazywany również TAG_DONE (równy 0). Właśnie ten
tag informuje funkcję, że nie ma już więcej
danych. Jeśli go zabraknie, to nieszczęsna funkcja
nadal będzie czytać dane z przypadkowego obszaru
pamięci i może się naczytać bzdur. Dlatego
uczulamy Was: PAMIĘTAJCIE o stawianiu tagu
TAG_END. Wykrycie takiego błędu nie zawsze jest
proste, gdyż program może czasami "chodzić"
całkiem normalnie (bo akurat znajdzie w pamięci
zero).
Obie opisane powyżej funkcje do otwierania okien
zwracają adres struktury opisującej otwarte okno
(struktura Window) lub NULL, w wypadku gdy spotka
je niepowodzenie.
Zastosowanie tych funkcji można zobaczyć tutaj:
Listing nr 4
Przypominamy, że listingi nie są kompletne! Należy
dołączyć funkcję check_os() z poprzedniej części
kursu oraz definicje stałych "OS_xx", deklarację
zmiennej "SysBase" i pliki nagłówkowe
"exec/execbase.h" oraz "stdlib.h".
Warto wytłumaczyć, co oznaczają użyte w tym
przykładzie tagi:
WA_Left -- współrzędna lewej krawędzi okna,
WA_Top -- współrzędna górnej krawędzi okna,
WA_Width -- rozmiar okna w poziomie,
WA_Height -- rozmiar okna w pionie.
W wypadku Amigi współrzędne są podawane względem
lewego górnego rogu, który ma współrzędne (0, 0),
ekran za jest położony niejako w czwartej ćwiartce
układu współrzędnych. Nie należy się sugerować
programami graficznymi, w wypadku których
wyświetlane współrzędne są przeliczane w taki
sposób, że ekran jest przez użytkownika widziany
jako pierwsza ćwiartka kartezjańskiego układu
współrzędnych. Ta sama zasada obowiązuje w wypadku
okien. Współrzędne w oknie oblicza się względem
lewego górnego rogu okna.
WA_Title -- tytuł okna (wyświetlany na jego
listwie),
WA_Flags -- flagi dotyczące głównie wyglądu okna:
WFLG_SIZEGADGET -- okno ma mieć gadżet do
zmieniania wielkości,
WFLG_DRAGBAR -- okno ma mieć listwę do
przesuwania za pomocą myszy,
WFLG_DEPTHGADGET -- okno ma mieć gadżet
przód/tył,
WFLG_CLOSEGADGET -- okno ma mieć gadżet
zamykania,
WFLG_ACTIVATE -- okno ma zostać automatycznie
uaktywnione po otwarciu,
WFLG_RMBTRAP -- naciskanie prawego przycisku
menu nie ma powodować
rysowania listwy menu.
Niektóre flagi mają swoje odpowiedniki wśród
tagów. Można je wywoływać bezpośrednio, podając po
nich wartość TRUE. Przykładami takich flag mogą
być: WA_SizeGadget, WA_DepthGadget,
WA_CloseGadget. Dokładny ich wykaz, wraz z
definicjami stałych, znajduje się w pliku
"intuition/intuition.h".
WA_ScreenTitle -- napis wyświetlany na górnej
listwie ekranu, gdy okno jest aktywne.
W programie została użyta pewna funkcja z
biblioteki "dos.library". Jej składnia jest bardzo
prosta:
void Delay( long timeout );
Za pomocą tej funkcji można chwileczkę odczekać.
Długość chwileczki jest podawana w jednostkach
nazywanych ticks, 50 takich jednostek to jedna
sekunda. Uważny Czytelnik zapewne się zastanawia,
jakim prawem wołamy funkcję z biblioteki
dos.library bez jej otwarcia. Możemy pozwolić
sobie na tę "zbrodnię", ponieważ bibliotekę tę
otwiera dla nas moduł startowy, dołączany przez
linkera, i podstawia pod zmienną "DOSBase" (więcej
o module startowym powiemy w przyszłości).
Można więc chyba uznać, że otwarcie okna mamy za
sobą. Teraz należy się nauczyć z nim komunikować.
Jest to rozwiązane za pomocą tak zwanych portów
(struktur MsgPort), będących czymś w rodzaju
skrzynek kontaktowych. Jeden program umieszcza w
porcie wiadomość (strukturę Message), a drugi po
chwili ją odbiera -- daje się tu wyraźnie odczuć
multitasking.
Program będący właścicielem okna sam decyduje,
jakie wiadomoci chce za jego pomocą uzyskiwać.
Może go np. zupełnie nie interesować, czy
użytkownik nacisnął jakiś klawisz na klawiaturze
albo czy wyjął dyskietkę ze stacji. To, jakie
wiadomości program ma otrzymywać, ustala się
podczas otwierania okna, za pomocą tagu "WA_IDCMP"
(ta "wpadająca w pamięć" nazwana jest skrótem od
Intuition Direct Communication Message Port). Jako
dane dla tego tagu podaje się stałe symboliczne,
oznaczające poszczególne klasy wiadomoci, jakie
program ma otrzymywać. Jest ich mnóstwo. Będziemy
je podawać stopniowo (żeby Was nie dobić). W tej
części użyjemy w przykładach następujących klas:
IDCMP_CLOSEWINDOW -- kliknięcie na gadżet
zamykania okna.
IDCMP_DISKINSERTED -- włożenie dysku.
IDCMP_DISKREMOVED - wyjęcie dysku.
IDCMP_MOUSEBUTTONS -- naciśnięcie bądź zwolnienie
któregoś z przycisków myszy (tylko przy aktywnym
oknie).
W wypadku okien adres portu znajduje się w
strukturze Window, w polu o nazwie "UserPort".
Program pragnący otrzymać wiadomość nie musi co
chwilę sprawdzać, czy użytkownik wykonał jaką
operację (np. kliknął myszą). Wystarczy, że
poprosi system o poinformowanie go, kiedy coś
takiego nastąpi. W programach bardzo często
zachodzi potrzeba oczekiwania na wydarzenie, do
tego celu służy funkcja Execa:
struct Message *WaitPort( struct MsgPort *port );
Funkcja ta oczekuje na pojawienie się
jakiejkolwiek wiadomości w podanym porcie, jest
ona wykonywana aż do czasu pojawienia się
wiadomości, wówczas przekazuje sterowanie z
powrotem do programu. Funkcja ta zwraca wskaźnik
na pierwszą wiadomość umieszczoną w porcie, choć
może być ich w porcie więcej. Należy pamiętać, że
funkcja ta czeka "do upadłego". Jeśli żadna
wiadomość do portu nie dotrze, to program dalej
"nie pójdzie". Nierzadkim błędem jest oczekiwanie
na wiadomoci od okna, które żadnych wiadomości
przysłać nie może (ma pole IDCMPFlags==0). Na
szczęście trudno taki błąd przegapić.
Kiedy już stwierdzimy, że w porcie jest jakaś
wiadomość, to należy ją odebrać. Posłużymy się w
tym celu funkcją Execa:
struct Message *GetMsg( struct MsgPort *port );
Funkcja ta usuwa z portu pierwszą ze znajdujących
się w nim wiadomoci i jej adres zwraca naszemu
programowi. W wypadku, gdy port jest pusty,
funkcja zwraca NULL.
W otrzymanej w ten sposób wiadomoci znajdziemy
wiele cennych informacji. Omówimy je za chwilę. W
wypadku operacji na portach ważny jest pośpiech,
tak więc istotne dla nas informacje należy sobie w
pomocniczych zmiennych zapamiętać, a wiadomoć jak
najszybciej zwrócić nadawcy. Należy pamiętać, aby
niczego w otrzymanej wiadomoci nie zmieniać, bo
mogą wystąpić problemy. Wiadomoć odsyłamy za
pomocą funkcji Execa:
void ReplyMsg( struct Message *message );
Funkcja ta odsyła wiadomość do "macierzystego"
portu. Po odesłaniu wiadomości NIE WOLNO już z
niej korzystać. System może zrobić z nią, co
zechce, np. zwolnić zajmowaną przez nią pamięć,
przez co dane by po prostu znikły i naczytalibyśmy
się bzdur.
W wypadku wiadomości pochodzących od okien mamy do
czynienia z rozszerzonymi strukturami Message o
nazwie IntuiMessage. Postępowanie jest takie samo,
jak w wypadku rozszerzonych struktur Library. I
tym razem pierwszym polem struktury IntuiMessage
jest struktura Message, można więc swobodnie
zastosować rzutowanie typów. Zawartością struktury
Message zajmiemy się póˇniej, przy dokładnym
omawianiu portów (są to zresztą systemowe
zawiłości, bez których można się obejść). Teraz
omówmy pozostałe pola struktury IntuiMessage.
Zajrzyjmy do niej -- znajduje się ona w pliku
"intuition/intuition.h". Najważniejsze pola to:
ULONG Class -- Informuje o klasie (rodzaju)
wiadomości, która dotarła do portu. Są to te same
stałe, których używa się wcześniej w tagu WA_IDCMP
(IDCMP_CLOSEWINDOW itd.).
UWORD Code -- Zawiera dodatkowe informacje,
zależne od rodzaju wiadomości, np. dla wiadomości
IDCMP_MOUSEBUTTONS pole to zawiera informacje o
tym, który przycisk myszy został wciśnięty lub
zwolniony (dokładniej o tym za chwilę). Niektóre
rodzaje wiadomości w ogóle nie używają tego pola,
np. IDCMP_CLOSEWINDOW.
UWORD Qualifier -- To pole zawiera nie
przetworzone informacje, dostarczone przez program
systemowy o nazwie input.device, zajmujący się
obsługą wejścia/wyjścia. Jest ono rzadko
wykorzystywane. Można tu znaleźć np. informacje o
tym, czy gdy wystąpiło zdarzenie, był wciśnięty
lewy klawisz [Shift] (IEQUALIFIER_LSHIFT), albo
czy dana cyfra została wpisana na klawiaturze
numerycznej (IEQUALIFIER_NUMERICPAD) itp. --
odpowiednie stałe są zdefiniowane w pliku
"devices/inputevent.h".
APTR IAddress -- Zawiera adres obiektu, będącego
przyczyną wysłania wiadomoci, jest zależne od jej
rodzaju, np. w wypadku wiadomoci o gadżetach
zawiera adres gadżetu.
WORD MouseX, MouseY --
Zawierają położenie myszy w chwili wystąpienia
zdarzenia, współrzędne są podawane względem lewego
górnego rogu okna. Jeśli w polu IDCMPFlags okna był
ustawiony znacznik IDCMP_DELTAMOVE, współrzędne
będą podawane względem poprzednio meldowanego
położenia (wektor przesunięcia).
ULONG Seconds, Micros -- Zawierają czas systemowy,
w którym zdarzenie nastąpiło (bardzo przydatne,
gdy chcemy sprawdzić, czy miał miejsce tzw.
double-click, czyli szybkie, dwukrotne, kliknięcie
myszą).
Jak już wcześniej pisaliśmy, wiadomości otrzymane
od Intuition należy jak najszybciej zwrócić. W
związku z tym najwygodniej jest skopiować całą
strukturę lub tylko te pola, które nas interesują.
Przetrzymywanie informacji od Intuition może
wpłynąć na spowolnienie działania systemu. Po
wykonaniu kopii i zwróceniu wiadomości możemy się
swobodnie i bez pośpiechu zastanawiać, co począć
dalej. Nieocenioną pomocą jest instrukcja
"switch". Na początku należy rozpoznać rodzaj
wiadomości, czyli zerknąć do pola "Class", a
następnie, w zależności od jego zawartości,
analizować pozostałe pola.
W wypadku gdy otrzymamy informację typu
IDCMP_CLOSEWINDOW, wiemy, co sygnalizuje
użytkownik -- pozostałe pola nie zawierają
istotnych informacji. Podobnie jest w wypadku
IDCMP_DISKINSERTED i IDCMP_DISKREMOVED -- nie ma
sensu patrzeć na zawartość pozostałych pól (jeżeli
ktoś nie pisze programu sprawdzającego, jak szybko
można zmienić dyski, wówczas potrzebne byłyby pola
Seconds i Micros, ale sadyzmu nie popieramy).
Zupełnie innaczej wygląda sytuacja z wiadomościami
o "przyciśnięciu myszy" (IDCMP_MOUSEBUTTONS). W
tym wypadku pole "Code" zawiera użyteczne
informacje:
SELECTDOWN -- wciśnięto lewy przycisk myszy,
SELECTUP -- zwolniono lewy przycisk myszy,
MENUDOWN -- wciśnięto prawy przycisk myszy,
MENUUP -- zwolniono prawy przycisk myszy,
MIDDLEDOWN -- wciśnięto środkowy przycisk myszy,
MIDDLEUP -- zwolniono środkowy przycisk myszy.
Wiadomości tej klasy docierają tylko do jednego
okna -- do tego, które jest aktywne.
Amiga obsługuje trzeci przycisk myszy, choć często
go nie ma, natomiast pecety mają go prawie zawsze,
ale i tak ogromna większość programów używa tylko
jednego przycisku -- lewego (dla zainteresowanych
dodam, że w Amidze trzeci przycisk myszy należy
podłączyć do wyprowadzenia numer 5 oraz do masy,
czyli wyprowadzenia numer 8).
Za miesiąc przedstawię wykorzystanie portu okna.