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".