Kurs jëzyka C (cz. 6.) ---------------------- MALOWANIE PO EKRANIE Kamil Iskra, Dariusz Ûbik Odcinek ten zawiera dokoïczenie omówienia listingu 9., który zamieôciliômy w poprzednim numerze Magazynu AMIGA. W naszym programie wystëpuje oczywiste powiâzanie pomiëdzy trybem pracy programu, klawiszem powodujâcym jego wywoîanie i opisujâcym go tekstem. Stworzyliômy wiëc wîasnâ strukturë o nazwie "operacja", która îâczy te trzy elementy: pole "name" zawiera opis trybu, a "key" literë wywoîujâcâ dany tryb. Trzecie pole -- "fun" -- moûe byê czymô nowym dla niezbyt doôwiadczonych programistów. Jest to wskaúnik na funkcjë pobierajâcâ argumenty "wskaúnik na strukturë IntuiMessage" oraz "wskaúnik na strukturë Window", a nie zwracajâcâ niczego (void). Peîniejsze wiadomoôci znajdziesz w ksiâûce "Jëzyk ANSI C". W programie definiowana jest tablica takich struktur: "op_tab", w pola "fun" jest wpisywany adres funkcji obsîugujâcych dany tryb programu, a to, w jakim trybie program znajduje sië w danym momencie, jest odnotowane w zmiennej "op_pos", której wartoôê jest po prostu indeksem tablicy "op_tab". Aby wywoîaê funkcjë, której adres znajduje sië w polu "fun", naleûy postâpiê tak samo, jakby sië wywoîywaîo normalnâ funkcjë. My w programie robimy po prostu: op_tab[op_pos].fun(&msg, window); Deklaracjë zmiennych mamy juû za sobâ, nastëpnie otwieramy biblioteki i okna, potem przychodzi czas na pëtlë "for", wykonujâcâ sië do koïca programu, oraz "rutynkë", czyli oczekiwanie na informacje z portu okna, a póúniej jeszcze jakieô "cuda": "__STDC__" i inne. Zastosowaliômy wyraûenie, które nie jest znane wszystkim kompilatorom, a mianowicie skopiowanie jednej struktury do drugiej za pomocâ pojedynczego przypisania. Taki zapis jest zgodny z normâ ANSI jëzyka C, ale niektóre starsze kompilatory mogâ mieê z nim problemy. Z tego powodu uûyliômy dyrektyw preprocesora, powodujâcych warunkowâ kompilacjë: jeûeli jest zdefiniowana staîa "__STDC__" i jest ona róûna od 0 (#if __STDC__), co oznacza, ûe dany kompilator jest zgodny z normâ ANSI, to uûywamy wyûej opisanej instrukcji, w przeciwnym wypadku (#else) jawnie kopiujemy jednâ strukturë do drugiej. Warto moûe w tym momencie wspomnieê o kilku innych standardowo zdefiniowanych staîych, np. __SASC i AMIGA dla SAS/C, AZTEC_C i MCH_AMIGA dla Aztec C, __GNUC__ i AMIGA dla GNU CC. Dziëki nim moûna tworzyê programy, dziaîajâce na kilku platformach sprzëtowych oraz moûliwe do skompilowania na róûnych kompilatorach. Wprawdzie nie wszyscy bëdâ zmuszeni do wywoîania funkcji CopyMem() z biblioteki Exec, jednak wszyscy powinni jâ znaê: void CopyMem( APTR source, APTR dest, unsigned long size ); Zadaniem funkcji jest skopiowanie obszaru pamiëci, wskazywanego przez wskaúnik "source", do obszaru wskazywanego przez "dest", rozmiar kopiowanego "kawaîka" podany w bajtach jest trzecim argumentem. UWAGA! Kolejnoôê parametrów "source" i "dest" jest ODWROTNA niû w standardowych funkcjach kopiujâcych jëzyka C, takich jak "strcpy", "memcpy" itd. Po wykonaniu kopii przybyîej wiadomoôci i zwrocie oryginaîu "rozpakowujemy prezent" od Intuition. Klasa wiadomoôci IDCMP_VANILLAKEY: Flaga ta powoduje dostarczanie do programu informacji o uûyciu klawiatury. Informacja tego typu zawiera w polu "Code" kod znaku, jaki znajdowaî sië pod naciôniëtym przyciskiem, obsîugiwane jest aktualne obîoûenie klawiatury, ustawione przez uûytkownika za pomocâ systemowego programu "Input". VANILLAKEY dostarcza tylko pojedyncze znaki, nie jest moûliwe odczytanie ciâgów zdefiniowanych pod przyciskami, nie moûna dowiedzieê sië o przycisku HELP, kursorach... Moûliwe jest jednak odczytanie wciôniëcia klawisza [Esc], poniewaû jest on pojedynczym znakiem o kodzie 27. W programie, po otrzymaniu informacji o naciôniëciu przycisku, sprawdzamy, czy jest to [Esc]. Jeôli tak, to opuszczamy program, w przeciwnym wypadku przeglâdamy tablicë operacji, sprawdzajâc, czy któraô z nich nie ma skrótu z klawiatury identycznego z naciôniëtym przyciskiem. Ten przydîugawy warunek w pëtli "for" pozwala przejrzeê wszystkie elementy tablicy "op_tab". Poniewaû operator "sizeof" zwraca rozmiar obiektu w bajtach, naleûy wielkoôê të podzieliê przez rozmiar pojedynczego elementu. Operator "sizeof" podaje rozmiar obiektu podczas kompilacji programu, w zwiâzku z tym wyraûenie w warunku pëtli "for" otrzyma staîâ wartoôê. Po otrzymaniu informacji IDCMP_CLOSEWINDOW wywoîujemy funkcjë: BOOL DoubleClick( ULONG sSeconds, ULONG sMicros, ULONG cSeconds, ULONG cMicros); Jej zadaniem jest sprawdzenie, czy podane dwie wartoôci czasu mieszczâ sië w tzw. double clicku. Jeôli róûnica miëdzy tymi wartoôciami jest mniejsza od "double clicku", to zostanie zwrócona wartoôê TRUE, w przeciwnym wypadku FALSE. Czas do funkcji podaje sië w postaci sekund i mikrosekund pierwszego i drugiego wydarzenia. Moûna je znaleúê w strukturze "IntuiMessage". Dziëki funkcji DoubleClick() program opuszcza sië dopiero po dwukrotnym klikniëciu na gadûecie zamykania. W wypadku pojedynczego naciôniëcia zostanie zmieniony tryb pracy programu (tak, wiemy, to nie jest intuicyjne). W wypadku, gdy system dostarczy informacji o przyciskach myszy (IDCMP_MOUSEBUTTONS), zostanie wywoîana bieûâca funkcja z tablicy "op_tab". Wewnâtrz instrukcji "switch" pojawiîa sië kolejna, nie omówiona dotychczas, klasa informacji IDCMP, a mianowicie IDCMP_MOUSEMOVE. Klasa ta powoduje przekazywanie do portu okna aktywnego informacji o kaûdym ruchu myszy (trzeba je obsîugiwaê szybko, bo przy nagîych ruchach myszâ przybywa naprawdë sporo informacji!). Do jej funkcjonowania konieczne jest umieszczenie w polu "Flags" okna flagi WFLG_REPORTMOUSE. Program w prezentowanej wersji nie robi nic w wypadku stwierdzenia takiej klasy wiadomoôci. Umieôciliômy jâ jednak, aby umoûliwiê rozbudowë programu, która bez znajomoôci tej klasy informacji mogîaby byê kîopotliwa. Poniewaû w funkcji "main" nie znajdziemy juû nic ciekawego, omówië funkcje zawarte w tablicy "op_tab": linia() -- zawiera funkcjë z biblioteki "graphics.library": void Draw( struct RastPort *rp, long x, long y ); Jej zadaniem jest narysowanie linii pomiëdzy obecnym poîoûeniem kursora a podanymi wspóîrzëdnymi, zmieniane jest poîoûenie kursora graficznego na wartoôci x i y. punkt() -- zawiera dwie funkcje biblioteczne: LONG WritePixel( struct RastPort *rp, long x, long y ); Zmienia kolor punktu o wspóîrzëdnych (x, y) na kolor ustawiony jako APen. Rezultatem dziaîania funkcji jest zero, gdy wszystko jest OK, lub -1, gdy podany punkt jest poza rastportem. Funkcja, jako jedna z niewielu, nie zmienia poîoûenia kursora graficznego. void Move( struct RastPort *rp, long x, long y ); Przesuwa kursor graficzny w podane miejsce, bez jakiegokolwiek efektu wizualnego. Funkcja napis() -- odwoîuje sië do funkcji systemowej: LONG Text( struct RastPort *rp, STRPTR string, unsigned long count ); Umieszcza ona tekst, wskazywany przez "string", w rastporcie. Napis jest umieszczany w bieûâcym miejscu (cp_x, cp_y) i rysowany bieûâcâ czcionkâ. Pisany jest zarówno poniûej, jak i powyûej poîoûenia kursora (dlaczego tak jest, powiemy w kolejnej czëôci). Po zakoïczeniu dziaîania funkcji kursor graficzny znajduje sië na koïcu tekstu. Funkcja ta wymaga podania liczby znaków do wypisania -- sîuûy do tego ostatni argument. drmd() -- funkcja ta zmienia tryb rysowania po naciôniëciu lewego przycisku myszy. Systemowe narzëdzia, sîuûâce do tego celu, opisaliômy wczeôniej. Wystëpujâca tu przydîuga i skomplikowana konstrukcja, bëdâca drugim argumentem funkcji SetDrMd(), powoduje, ûe tryb rysowania zostanie ustalony na kolejny lub odliczanie rozpocznie sië od poczâtku. Postëpujâc zgodnie z kolejnoôciâ operatorów, najpierw zostanie wykonana konstrukcja warunkowa, zwracajâca numer obecnego trybu (jej zadanie byîo omówione wczeôniej) -- po jego zwiëkszeniu o jeden otrzymamy numer kolejnego trybu. Poniewaû argument funkcji SetDrMd(), bëdâcy równoczeônie indeksem tablicy names[], powinien byê istniejâcym trybem, naleûy zadbaê o to, aby wartoôê ta nie przekraczaîa 7, poniewaû po zsumowaniu masek bitowych wszystkich trybów rysowania otrzymamy takâ wîaônie wartoôê (patrz "graphics/rastport.h"). Do zapisania liczby z przedziaîu 0..7 wystarczâ trzy najmîodsze bity -- wîaônie w tym celu wykonujemy bitowâ operacjë I (AND) z flagâ 7 (bitowo 0111). Dziëki temu przetrwajâ jedynie najmîodsze bity i zapisanie liczby wiëkszej od 7 nie bëdzie moûliwe, uzyskamy wiëc cyklicznie zmieniajâcy sië tryb graficzny. Niektórzy z Was zapewne zastanawiajâ sië, dlaczego nie zastosowaliômy tu prostszej i bardziej ogólnej metody z wyraûeniem warunkowym "if" -- po prostu tak, jak jest, jest szybciej i krócej. color() -- zmienia kolor tîa po przyciôniëciu prawego przycisku oraz atramentu po przyciôniëciu lewego. kwadrat() -- rysuje wypeîniony prostokât od poîoûenia kursora graficznego do poîoûenia myszy w momencie klikniëcia. Zwie sië niezbyt zgodnie z prawdâ, poniewaû kolega Darek nie odróûnia prostokâtów od kwadratów. Funkcja ta korzysta z funkcji biblioteki graficznej: void RectFill( struct RastPort *rp, long xMin, long yMin, long xMax, long yMax ); Jej zadaniem jest wypeînienie prostokâtnego obszaru za pomocâ aktualnego koloru APen. Wspóîrzëdne lewego górnego rogu prostokâta (xMin, yMin) MUSZÂ byê mniejsze od wspóîrzëdnych prawego dolnego rogu (xMax, yMax) lub im równe. elipsa() -- funkcja rysuje elipsë, dîugoôci póîosi sâ równe wspóîrzëdnym wektora îâczâcego punkt (cp_x, cp_y) z punktem, nad którym "przyciôniëto" mysz. Biblioteczna funkcja do rysowania elipsy wyglâda nastëpujâco: void DrawEllipse( struct RastPort *rp, long xCenter, long yCenter, long a, long b ); xCenter i yCenter to wspóîrzëdne ôrodka elipsy, a i b to póîosie elipsy (muszâ byê wiëksze od zera). Wspóîrzëdne cp_x i cp_y nie sâ zmieniane. Za miesiâc pokaûemy, jak uûywaê róûnych czcionek oraz jak tworzyê górne menu.