****************************************** UWAGA DO SKÎADU: PROSZË PRZY WLEWANIU TEGO TEKSTU WYÎÂCZYÊ KONWERSJË CUDZYSÎOWÓW, TAK, ABY PAGEMARKER NIE ZMIENIAÎ POCZÂTKOWYCH CUDZYSÎOWÓW NA DOLNE. ****************************************** 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. Kamil Iskra, Dariusz Ûbik 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ê w listingu 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 (RMB -- Right Mouse Button) 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 wiadomoôci 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 wiadomoôci, 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 wiadomoôci 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 wiadomoôci i jej adres zwraca naszemu programowi. W wypadku, gdy port jest pusty, funkcja zwraca NULL. W otrzymanej w ten sposób wiadomoôci 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 wiadomoôci 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 wiadomoôci, jest zaleûne od jej rodzaju, np. w wypadku wiadomoôci 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.