C dla każdego (cz. 13.)

Gadżety

Zgodnie z obietnicą, dziś zabieramy się wreszcie do gadżetów -- najważniejszego po ekranach i oknach fragmentu graficznego interfejsu użytkownika.

Gadżety, jak zresztą całe GUI, są zarządzane przez bibliotekę Intuition. Tak samo jednak, jak w wypadku górnego menu, biblioteka "gadtools.library" zawiera kilka funkcji w niesłychany wręcz sposób upraszczających tworzenie gadżetów oraz późniejsze zarządzanie nimi. Wypada wiedzieć, że w OS 1.3 tworzenie bardziej skomplikowanych gadżetów było udręką programistów!
Trzeba było samodzielnie zdefiniować dla każdego gadżetu strukturę "Gadget", opisującą rozmiar gadżetu i jego typ, strukturę "Border", opisującą rozmiar i kształt ramki wokół gadżetu, strukturë "IntuiText", opisującą tekst gadżetu i jego ułożenie, nierzadko równieű dodatkowe struktury, jak "StringInfo", "PropInfo" itd. Tymczasem, korzystając z "gadtools.library", wystarczy zdefiniować jedną prostą strukturę dla każdego gadżetu, a otrzymujemy rozplanowany gadżet o przyzwoitym, trójwymiarowym wyglądzie. Będzie to z pewnością pewnym uproszczeniem, ale nie pomylë się chyba wiele twierdząc, że właśnie pojawienie się w OS 2.0 biblioteki "gadtools.library" spowodowało, że przestały powstawać programy działające pod OS 1.3.

Struktura zawierająca informacje niezbędne do stworzenia gadżetu znajduje się w pliku "libraries/gadtools.h" i wygląda tak:

struct NewGadget
{
WORD ng_LeftEdge, ng_TopEdge;
WORD ng_Width, ng_Height;
UBYTE *ng_GadgetText;
struct TextAttr *ng_TextAttr;
UWORD ng_GadgetID;
ULONG ng_Flags;
APTR ng_VisualInfo;
APTR ng_UserData;
};

Znaczenie pól jest następujące:

ng_LeftEdge, ng_TopEdge, ng_Width, ng_Height -- współrzędna lewego górnego rogu gadżetu oraz jego rozmiar.

ng_GadgetText -- tekst opisujący gadżet. Zaleca się, aby w wypadku gadżetów-przycisków, których naciśnięcie powoduje otwarcie jakiegoô okienka, tekst ten był zakończony trzema kropkami -- ułatwia to użytkownikowi orientację.

ng_TextAttr -- wskaźnik na strukturë "TextAttr", opisującą czcionkę, jaką mają być wypisywane teksty w gadżecie.

ng_Flags -- flagi odnoszące się do tekstu, opisującego gadżet. Flagi PLACETEXT_LEFT, PLACETEXT_RIGHT, PLACETEXT_ABOVE, PLACETEXT_BELOW i PLACETEXT_IN ustalają, w którym miejscu ma się znaleźć ten tekst (odpowiednio: po lewej, po prawej, powyżej, poniżej, wewnątrz); flaga NG_HIGHLABEL powoduje, że tekst "ng_GadgetText" zostanie wypisany jasnym kolorem.

ng_VisualInfo -- wskaźnik na prywatną strukturę uzyskaną za pomocą funkcji GetVisualInfo(), którą opisaliśmy przy okazji górnego menu.

ng_GadgetID, ng_UserData - pola pozostawione dla nas, do wykorzystania w dowolny sposób. W "ng_GadgetID" umieszcza się zwykle numer gadżetu. Po tym można później rozpoznać, który gadżet został wciśnięty. "ng_UserData" może np. wskazywać na funkcję, która ma zostać wywołana po wciśnięciu danego gadżetu.

Tworzenie listy gadżetów za pomocą GadTools jest dwuetapowe:

Najpierw należy wywołać funkcję:

struct Gadget *CreateContext( struct Gadget **glistptr );

Funkcja ta tworzy "zaczątek" listy gadżetów, w którym inne funkcje biblioteki GadTools będą odnotowywały prywatne informacje. Parametr wygląda może groźnie -- "wskaźnik na wskaźnik na gadżet". Nie należy się go jednak bać. Jako parametr funkcji należy podać po prostu adres wyzerowanej wcześniej zmiennej, będącej wskaźnikiem na gadżet -- wartość tej zmiennej zostanie zmieniona. Funkcja zwraca adres "zaczątku" lub, jak to zwykle bywa, NULL w wypadku niepowodzenia.

Po wykonaniu powyższych operacji należy dla każdej struktury "NewGadget" z osobna wywołać funkcję:

struct Gadget *CreateGadgetA( unsigned long kind, struct Gadget *gad, struct NewGadget *ng, struct TagItem *taglist );
struct Gadget *CreateGadget( unsigned long kind, struct Gadget *gad, struct NewGadget *ng, Tag tag1, ... );

"kind" oznacza rodzaj gadżetu, jaki ma zostać utworzony (o tym za chwilë).

"gad" to wskaźnik na poprzedni gadżet, tzn. na gadżet utworzony przy poprzednim wywołaniu tej funkcji lub na "zaczątek" przy jej pierwszym wywołaniu.

"ng" to wskaźnik na opisaną wcześniej strukturę "NewGadget", która opisuje podstawowe atrybuty gadżetu do utworzenia.

Funkcja akceptuje również wiele tagów, niemal wszystkie jednak zależą od rodzaju gadżetu, który ma zostać utworzony. Istnieją tylko dwa tagi "uniwersalne":

GT_Underscore -- daną tego taga jest litera (char). Ustala ona, jaka litera w tekście pola "ng_GadgetText" struktury "NewGadget" ma być uznawana za identyfikator podkreślenia -- właściwie zawsze jako wartość tego taga ustala się '_'. Użycie w tekście "ng_GadgetText" podkreślnika spowoduje, że znajdująca się za nim litera zostanie wyświetlona jako podkreślona, co jest używane do poinformowania użytkownika o "skrócie z klawiatury" dla danego gadżetu. Standardowo żadna litera nie jest identyfikatorem podkreślenia.

GA_Disabled -- dana taga jest typu BOOL. Gdy jest ona niezerowa, to gadżet zostanie utworzony jako "wyłączony", tzn. niemożliwy do obsługi za pomocą myszy -- "za mgiełką". Standardowo gadżet jest tworzony jako włączony.

Czeka nas teraz to, czego nie lubimy i staramy się w tym kursie unikać, a mianowicie długa nudna lista stałych -- niestety, w tym wypadku inaczej postąpić nie mogliśmy... Oto rodzaje gadżetów oferowane przez GadTools oraz najważniejsze z akceptowanych przez nie tagów:

BUTTON_KIND -- klasyczny gadżet-przycisk, jak np. Save w preferencjach.

CHECKBOX_KIND -- gadżet-fajeczka, do włączenia bądź wyłączenia jakiejô opcji. Tagi:
GTCB_Checked -- dana typu BOOL ustala, czy gadżet jest zaznaczony (ma fajeczkę), czy nie. Standardowo nie jest zaznaczony.
GTCB_Scaled -- dana typu BOOL ustala, czy gadżet może mieć dowolny rozmiar -- w OS 2.0 gadżety "fajeczkowe" mogły mieć tylko jeden ustalony rozmiar 26 x 11, w OS 3.0 usunięto to ograniczenie -- gadżety te mogą być skalowalne, ale tylko wtedy, gdy został użyty ten tag z daną niezerową (najlepiej po prostu podać TRUE). Patrz też obrazek, na którym pokazałem główne różnice między gadżetami w OS 2.04 i OS 3.00.

STRING_KIND, INTEGER_KIND -- gadżety do wprowadzania informacji z klawiatury, odpowiednio tekstu lub liczby całkowitej dziesiętnej (może być ujemna). Tagi:
GTST_String, GTIN_Number -- początkowa zawartość gadżetu (odpowiednio: wskaźnik na ciąg znaków lub liczba typu long). Standardowo odpowiednio: napis pusty albo 0.
GTST_MaxChars, GTIN_MaxChars -- maksymalna liczba znaków, jaką można wpisać w gadżet. Standardowo odpowiednio: 64 albo 10.
STRINGA_Justification -- ustala, czy wpisywany tekst ma być dosunięty do lewej krawędzi (GACT_STRINGLEFT -- standardowe), do prawej (GACT_STRINGRIGHT), czy teű ma być centrowany (GACT_STRINGCENTER).

TEXT_KIND, NUMBER_KIND -- gadżety do wyświetlania napisu lub liczby całkowitej dziesiętnej. Nie można ich wyłączać (GA_Disabled). Nie miałoby to zresztą większego sensu -- nie obsługuje się ich myszką. Z tego samego powodu nie "słuchają" też taga GT_Underscore. Tagi:
GTTX_Text, GTNM_Number -- to samo, co wcześniej z GTST_String i GTIN_Number.
GTTX_Border, GTNM_Border -- dana typu BOOL ustala, czy gadżet ma być otoczony wklęsłą trójwymiarową ramką (standardowo nie).

LISTVIEW_KIND -- gadżet-lista do wyświetlania obszernej listy pozycji i ewentualnego wybrania jednej z nich, wyposażony w gadżety do przesuwania jego zawartości, np. gadżet do wyboru drivera drukarki w preferencjach. Gadżety te można wyłączać od OS 3.0. O ile niemal wszystkie pozostałe gadżety mogą mieć dowolne rozmiary (przynajmniej od OS 3.0 -- patrz opis CHECKBOX_KIND i MX_KIND), to wysokości gadżetów-list nie można powiększyć czy pomniejszyć o 1 punkt -- wysokość jest zawsze taka, aby w polu selekcji zmieściła się całkowita liczba pozycji, w razie potrzeby podana w polu "ng_Height" wysokość jest zaokrąglana w dół. Tagi:
GTLV_Labels -- daną jest wskaźnik na strukturę "List", którą poznaliście w poprzedniej części. W liście tej powinny być zapamiętane poszczególne pozycje.
GTLV_ReadOnly -- dana typu BOOL ustala, czy gadżet ma być przeznaczony tylko do przeglądania, tzn. czy można klikać na poszczególne pozycje, czy nie. Standardowo można klikać.
GTLV_ShowSelected -- jeżeli ten tag zostanie użyty, to wybrana w danym momencie pozycja będzie wyświetlana. Daną taga jest wskaźnik na strukturę "Gadget", opisująca stworzony wcześniej gadżet typu STRING_KIND -- po zaznaczeniu pozycji w liście jej tekst będzie automatycznie kopiowany do wskazanego gadżetu, co umożliwia np. edycję listy. Jeżeli jako daną poda się NULL, to wybrana pozycja będzie zaznaczona w inny sposób: pod OS 2.0 na dole listy będzie istniał gadżet typu TEXT_KIND, w którym będzie wyświetlana wybrana pozycja, a od OS 3.0 wybrana pozycja będzie po prostu namalowana innym kolorem (patrz obrazek).
GTLV_Selected -- ustala wybraną początkowo pozycję w liście. Należy podać numer porządkowy pozycji w liście (zaczynając od 0) bądź -1, aby nie mieć żadnej pozycji zaznaczonej (standardowo -1).
GTLV_Top, GTLV_MakeVisible -- tych dwóch tagów używa się w połączeniu z "GTLV_Selected". Otóż zazwyczaj zależy nam, aby wybrana pozycja była widoczna w liście -- przynajmniej na początku, po otwarciu okna. Jeżeli lista pozycji jest na tyle długa, że nie wszystkie mogą być wyświetlone na raz, a pozycja wybrana znajduje się gdzieś pod koniec listy, to nie będzie ona widoczna, co może wprowadzić użytkownika w błąd. Należy więc odpowiednio przesunąć listę. W OS 2.0 robi się to za pomocą taga "GTLV_Top", ustalającego numer pozycji, która ma być wyświetlana w gadżecie jako pierwsza -- jako daną podaje się po prostu to samo, co w tagu "GTLV_Selected". Autorzy systemu twierdzą, że podana wartość zostanie "zaokrąglona", jeżeli będzie wykraczała poza dopuszczalne granice. Mają rację, ale niezupełnie, bo wykryłem jeden problem -- nie radzę podać wartości "-1"... Metoda ta jest jednak ogólnie niezbyt elegancka, o czym można się łatwo przekonać po prostu patrząc, jak to działa. Dlatego w OS 3.0 pojawił się tag "GTLV_MakeVisible", który powoduje przesunięcie listy tylko o tyle pozycji, aby podana pozycja stała się widoczna (podaje się to samo, co w tagu "GTLV_Selected"); nie występuje też problem z wartością "-1".

CYCLE_KIND -- nazywany przeze mnie "gadżet-kręciołek", używany do wyboru jednej z kilku pozycji, przy czym tylko jedna na raz jest wyświetlana, np. gadżet do wyboru rodzaju papieru w preferencjach drukarki. Tagi:
GTCY_Labels -- daną jest tablica wskaźników na napisy, której ostatnim elementem jest NULL. Napisy te to kolejne pozycje wyświetlane wewnątrz gadżetu. Użycie tego taga jest OBOWIĄZKOWE.
GTCY_Active -- numer pozycji początkowo aktywnej (po prostu indeks w tablicy przekazanej przez "GTCY_Labels"). Standardowo 0.

MX_KIND -- tzw. przyciski radiowe -- o funkcji takiej samej, jak gadżety poprzedniego rodzaju, ale zajmują więcej miejsca w oknie, bo wyświetlane są wszystkie możliwości na raz, np. gadżet do wyboru parzystości w preferencjach portu szeregowego. Gadżety te można wyłączać od OS 3.0. Tagi:
GTMX_Labels, GTMX_Active -- to samo, co wcześniej z GTCY_Labels i GTCY_Active.
GTMX_Spacing -- odstęp w pionie między pozycjami, podany w punktach -- standardowo 1.
GTMX_Scaled -- to samo, co wcześniej z "GTCB_Scaled" (no, może poza tym, że rozmiar gadżetów w OS 2.0 wynosi 17 x 9). Patrz obrazek.
GTMX_TitlePlace -- ten tag pojawił się w OS 3.0. Jest tu drobne zamieszanie: w OS 2.0 gadżety te nie miały tekstu, opisującego gadżet, tzn. pole "ng_GadgetText" w strukturze "NewGadget" było ignorowane, a "ng_Flags" ustalało, po której stronie miały być wyświetlane teksty, opisujące poszczególne pozycje. W OS 3.0 autorzy systemu postanowili jednak dodać możliwość wyświetlania tekstu opisującego gadżet. Dla zachowania zgodności nie można było zmienię znaczenia pola "ng_Flags", dodano więc ten tag -- ustala on to, co dla innych rodzajów gadżetów ustala pole "ng_Flags"; tekst opisujący gadżet podany w polu "ng_GadgetText" zostanie wyświetlony tylko wtedy, gdy tag ten zostanie użyty.

To wszystko na dzisiaj. Za miesiąc dokończymy tę długą listę. Przedstawimy też pierwszą część listingu, ukazującego wykorzystanie gadżetów.