C dla kaûdego (cz. 7.) ---------------------- MALOWANIA CIÂG DALSZY W dzisiejszym odcinku opiszemy to, co zaprezentowane zostaîo w listingu z poprzedniej czëôci. Kamil Iskra, Dariusz Ûbik 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 otwarcie bibliotek i okna, pëtla "for", wykonujâca sië do koïca programu, oraz "rutynka", czyli oczekiwanie na informacje z portu okna, a póúniej jakieô "cuda": "__STDC__" i inne. Zastosowaliômy wyraûenie, które nie jest znane wszystkim kompilatorom, a mianowicie przypisanie 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 temu 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 woîania funkcji CopyMem() z biblioteki Exec, jednak wszyscy znaê jâ powinni: 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 przyciôniëtym przyciskiem. Obsîugiwane jest aktualne obîoûenie klawiatury, ustawione przez uûytkownika za pomocâ systemowego programu "Input". VANILLAKEY dostarcza tylko pojedynczych znaków. 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 woîamy funkcjë: BOOL DoubleClick( ULONG sSeconds, ULONG sMicros, ULONG cSeconds, ULONG cMicros); Jej zadaniem jest sprawdzenie, czy dwa podane czasy mieszczâ sië w tzw. double-clicku. Jeôli róûnica pomiëdzy podanymi czasami 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. Czas moûna znaleúê w strukturze "IntuiMessage". Dziëki funkcji DoubleClick() opuszczenie programu nastâpi 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 aktywnego okna 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ê jego rozbudowë, która bez znajomoôci tej klasy informacji mogîaby byê kîopotliwa. Poniewaû w funkcji "main" nie znajdziemy juû nic ciekawego, omówië jedynie 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 ); Rysuje punkt o wspóîrzëdnych (x, y). Funkcja zwraca zero, gdy wszystko byîo OK, lub -1, gdy podany punkt jest poza RastPortem. 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. 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â. Jest pisany zarówno poniûej, jak i powyûej poîoûenia kursora (dlaczego tak jest, powiemy za chwilë). 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 przyciôniëciu lewego przycisku myszy. Systemowe narzëdzia, sîuûâce do tego celu, opisaliômy wczeôniej. Wystëpujâca tu przydîugawa i skomplikowana konstrukcja, która jest 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. 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. Uznaje aktualne kolory i tryb rysowania. Wspóîrzëdne lewego górnego rogu prostokâta (xMin, yMin) MUSZÂ byê mniejsze lub równe od wspóîrzëdnych prawego dolnego rogu (xMax, yMax). 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 zajmë sië czcionkami.