C dla każdego (cz. 16.)
Gadżety (4)
Bez przydługich wstępów dokończmy opis poprzednio
przedstawionego fragmentu
listingu 13.
Zostało nam do opisania, co robimy w funkcji
stworzgadzety() dla gadżetu-suwaka:
Gadżetu tego będziemy używać do zmiany składowej
"Red" koloru zaznaczonego w gadżecie-palecie.
Musimy więc wiedzieć, w jakich granicach składowa
ta może się zmieniać -- zależy to, jak wiadomo, od
kości graficznych, trybu wyświetlania itd.
Potrzebujemy w tym celu dwóch funkcji z biblioteki
graficznej:
LONG GetVPModeID( struct ViewPort *vp );
Funkcja podaje identyfikator trybu wyświetlania
(patrz cz. 4. -- MA 8/95) podanego ViewPortu,
który jest częścią struktury "Screen" (patrz cz.
5., oznaczona omyłkowo jako cz. 4. -- MA 9/95).
ULONG GetDisplayInfoData( DisplayInfoHandle
handle, UBYTE *buf, unsigned long size, unsigned
long tagID, unsigned long displayID );
Funkcja może udzielić różnego rodzaju informacji o
trybie wyświetlania, podanym albo przez parametr
"handle" (przez nas nie używany -- podajemy 0),
albo przez "displayID" (podajemy wartość zwróconą
przez GetVPModeID()). My chcemy informacji typu
"DisplayInfo", dlatego też jako "buf" podajemy
wskaźnik na tę strukturę -- zostanie ona
wypełniona danymi, jako "size" rozmiar tej
struktury, a jako "tagID" DTAG_DISP (ten właśnie
parametr informuje funkcję, że chcemy informacji
typu "DisplayInfo").
Z otrzymanych danych chcemy wyłuskać informację o
liczbie bitów dla składowej "Red" -- zapamiętujemy
je w zmiennej globalnej "redbits". Pod OS 3.0+
jest łatwo: wystarczy skopiować zawartość pola
"RedBits" struktury "DisplayInfo". Pod OS 2.0x nie
mamy takich wygód i musimy korzystać z pola
"PaletteRange", zawierającego informacje o liczbie
możliwych wartości składowych koloru (nie ma
rozróżnienia na Red, Green, Blue). Aby otrzymać z
tej wartości liczbę bitów, musimy ją
zlogarytmizować przy podstawie 2. Wykorzystujemy
do tego bardzo prostą, przez nas napisaną funkcję
"log2()". Moglibyśmy oczywiście skorzystać z
bibliotecznej funkcji "log()". Jest ona jednak,
jak na nasze potrzeby, za "mądra": operuje na
liczbach zmiennoprzecinkowych i logarytmach
naturalnych, korzystając z różnych analitycznych
mądrości w stylu ciągów Maclaurina -- jest, innymi
słowy, kolubryną.
Definiujemy również funkcję "getrgb()", która dla
podanego numeru koloru wypełnia podane jako
parametry zmienne informacjami o składowych RGB
koloru. Korzystamy w niej z dwóch nie omówionych
dotychczas funkcji biblioteki graficznej:
ULONG GetRGB4( struct ColorMap *colorMap, long
entry );
void GetRGB32( struct ColorMap *cm, unsigned long
firstcolor, unsigned long ncolors, ULONG *table );
Są one dopełnieniem do poznanych w części 5. (MA
9/95) funkcji SetRGB4() i SetRGB32(): umożliwiają
pobranie informacji o obecnym ustawieniu
składowych Red, Green, Blue. Jako pierwszy
parametr -- wskaźnik na strukturę "ColorMap" --
podajemy pole "ColorMap" ViewPortu ekranu.
Funkcji GetRGB4() należy podać jako parametr
"entry" numer koloru -- zwraca ona 12-bitową
maskę, zawierającą w bitach 0-3 wartość Red, 4-7
Green, a 8-11 Blue.
Funkcja GetRGB32() pojawiła się w OS 3.0 i jest
znacznie bardziej rozbudowana. Umożliwia uzyskanie
informacji o więcej niż jednym kolorze naraz.
Parametr "firstcolor" oznacza numer pierwszego
koloru, a "ncolors" -- liczbę kolorów, o których
chcemy mieć informacje. Jako parametr "table"
należy podać wskaźnik na tablicę "ULONG-ów" -- ich
ilość nie powinna być mniejsza niż 3*ncolors.
Funkcja wpisuje tam wartości w formacie
oczekiwanym przez SetRGB32(), a więc 0 -- minimum
nasycenia, 0xFFFFFFFF -- maksimum nasycenia.
Pasuje nam to dla składowych "green" i "blue",
które będziemy tylko przekazywać do SetRGB32().
Jednak w przypadku "red" potrzebna jest nam
wartość w formacie od 0 do "maksymalne nasycenie"
(15 dla kości ECS, 255 dla AGA). Wydawać by się
mogło, że wystarczy rozwiązać prostą proporcję --
jedno mnożenie, jedno dzielenie. Tu jednak pojawia
się problem -- wynik mnożenia, np. 0x1FFFFFFF
przez 256, nie mieści się na 32 bitach.
Rozwiązaniem byłoby przejście na liczby
zmiennoprzecinkowe "double", bądź w kompilatorze
GNU CC na 64-bitowe liczby całkowite "long long".
Ponieważ jednak oba te rozwiązania pociągną za
sobą nieprzyjemne wydłużenie kodu wykonalnego,
postępujemy jeszcze inaczej -- korzystamy z
przesunięć bitowych.
Czas wreszcie wziąć się do opisu interakcyjnej
obsługi gadżetów.
Przy korzystaniu z GadTools jest to dość proste.
Zamiast funkcji Execa GetMsg() i ReplyMsg() należy
używać funkcji biblioteki GadTools:
struct IntuiMessage *GT_GetIMsg( struct MsgPort
*iport ); void GT_ReplyIMsg( struct IntuiMessage
*imsg );
Po co te funkcje? Zajmują się one filtrowaniem
nadchodzących do portu okna wiadomości. Nie każda
akcja przeprowadzona przez użytkownika ma
znaczenie dla programu, np. przeskrolowanie
zawartości w gadżecie-liście. GadTools zajmuje się
obsługą wiadomości nadchodzących od gadżetów, np.
po kliknięciu na jakąś pozycję w gadżecie-liście
automatycznie czyszczona jest zaznaczona
poprzednio pozycja i rysowana jest nowo zaznaczona
-- nasz program wcale nie robi tego sam, dzięki
czemu jest krótszy. Poza tym GadTools dostarcza do
naszego programu wiadomości od gadżetów w formie
nieco zmienionej, znacznie prostszej do obsługi,
np. podaje bezpośrednio numer pozycji, która
została wybrana w gadżecie-liście. Wyobraźcie
sobie, ile wysiłku wymagałaby "ręczna" obsługa
gadżetu-listy. Taki gadżet składa się w
rzeczywistości przynajmniej z czterech gadżetów:
pola selekcji pozycji, gadżetu-suwaka i dwóch
strzałek. Przy każdym wciśnięciu strzałki trzeba
by skrolować widoczne pozycje, przesuwać suwak,
brać pod uwagę przypadek, że już się niżej nie da
przesunąć itd., itp. GadTools oszczędza nam tego
wszystkiego. Do naszego programu dostarczane są
wiadomości klasy:
IDCMP_GADGETUP -- informuje o wykonaniu jakiejś
akcji na gadżecie. Pole "IAddress" struktury
"IntuiMessage" zawiera adres struktury "Gadget",
opisującej dany gadżet. Pola "GadgetID" oraz
"UserData" struktury "Gadget" zawierają to samo,
co pola "ng_GadgetID" i "ng_UserData" struktury
"NewGadget", po nich więc można gadżet rozpoznać.
Zawartość pola "Code" struktury "IntuiMessage"
zależy od rodzaju gadżetu, od którego przybyła
wiadomość, zostanie to omówione w kolejnej części.
Nie wszystkie gadżety przesyłają wiadomości klasy
IDCMP_GADGETUP. "Wyłamują się" gadżety wzajemnie
się wykluczające (MX_KIND) -- przesyłają
IDCMP_GADGETDOWN, jak również gadżety-suwaki
(SLIDER_KIND) i gadżety skrolujące (SCROLLER_KIND)
-- przesyłają IDCMP_MOUSEMOVE. Sposób obsługi tych
klas wiadomości niczym się jednak nie różni od
IDCMP_GADGETUP.
Aby obsługa gadżetów GadTools działała poprawnie,
muszą być w oknie ustawione odpowiednie flagi
IDCMP. W pliku "libraries/gadtools.h" zostały
zdefiniowane odpowiednie stałe, o nazwach
LISTVIEWIDCMP, CYCLEIDCMP itd., które specyfikują,
jakie flagi są danemu rodzajowi gadżetów
niezbędne.
Na tym musimy już dzisiaj zakończyć. Miejmy
nadzieję, że kolejna część będzie już ostatnią z
cyklu "gadżety".