Pierwsze dziewięć odcinków tego cyklu, była - choć może się to wydać zaskakujące - jedynie wstępem do tego co tygrysy lubią najbardziej. Do tej pory korzystaliśmy z MUI jak z zestawu gadżetów, na dobrą sprawę zupełnie w taki sam sposób jak na przykład z biblioteki GadTools. Jednak siła MUI tkwi w jego obiektowej strukturze, w możliwości korzystania z wbudowanych klas jako bazy do tworzenia własnych. Otwiera to przed nami niesamowite możliwości, często niespotykane nawet w "wiodących systemach". Warto więc wykonać śmiały krok aby nad tym bogactwem zapanować.
Na początek polecam powrót do pierwszego odcinka cyklu i odświeżenie sobie w pamięci podanych tam wiadomości, ponieważ okażą się teraz bardzo potrzebne. Jak wtedy napisałem każda klasa składa się z kilku podstawowych elementów. Są to: metody, atrybuty, dane klasy, dane obiektu i dispatcher. Ich określenie będzie teraz naszym zadaniem. Na pewno będzie trzeba napisać dispatcher, być może przedefiniować jakieś metody, napisać nowe, zdefiniować strukturę obiektu, a może klasy. Generalnie jest tu kilka stopni wtajemniczenia:
Oczywiście na poączątku zajmę się czymś prostym, a więc drobną modyfikacją istniejącej klasy. Zapewne każdy bawił się programem Slidorama dołączonym do MUI. Na dole jest taki zabawny suwak, tekst na jego "suwadle" zmienia się w zależności od ustawionej wartości. Takie zachowanie zostało uzyskane przez napisanie klasy potomnej dziedziczącej po klasie Slider i podstawienie jednej z metod. Na tym przykładzie wyjaśnię więc
Najpierw spójrzmy na drzewo klas MUI umieszczone w pliku nagłówkowym "mui.h". Klasa Slider jest potomną od klasy Numeric. Klasa Numeric zaś posiada metodę MUIM_Numeric_Stingify. Metoda ta służy do zamiany aktualnej wartości suwaka na tekst umieszczany na ruchomej części gadżetu. Oczywiście ta metoda jest umieszczona w kodzie klasy Numeric i składa się z czegoś w rodzaju "sprintf (bufor,"%ld",wartość);", a więc prostej zamiany liczby na łańcuch tekstowy. W opisie tej metody pada stwierdzenie, że jej wywoływanie z poziomu programu nie ma sensu i rzeczywiście tak jest. Po co więc została ona opisana? Otóż właśnie po to, żebyśmy w razie potrzeby mogli wyprowadzić klasę pochodną od Numeric (najczęściej pośrednio, np. poprzez klasę Slider) i podstawić tą metodę swoją własną. Systematyczni czytelnicy tego kursu z pewnością pamiętają program przykładowy do odcinka o notyfikacjach, z suwakiem i polem tekstowym, które powiadamiało o ustawieniu liczby 13 tekstem "Pechowa liczba!". Teraz spowodujemy, że suwak po prostu sam uniknie pokazania trzynastki w zamian wyświetlając sobie dwie kreski - "--". Pierwszym krokiem do podstawienia metody będzie oczywiście
który skieruje program do naszej wersji metody MUIM_Numeric_Stringify, zamiast do wersji oryginalnej. Ograniczmy teoretyzowanie do minimum - oto przykładowy kod:
__saveds long Dispatcher (Class *cl reg(a0), Object *obj reg(a2), Msg msg reg(a1)) { switch (msg->MethodID) { case MUIM_Numeric_Stringify: return (Stringify (cl, obj, msg)); default: return (DoSuperMethodA (cl, obj, msg)); } }
Dispatcher jest funkcją wywoływaną niejako "z wnętrza" systemu, dlatego jego parametry są przakazywane w rejestrach procesora. W takich przypadkach niektóre kompilatory mogą wymagać podania przed funkcją modyfikatora "__asm". Oprócz tego w różnych kompilatorach w różny sposób oznacza się przekazywanie parametrów w rejestrach. Wyrażenie "reg(xx)" to makro, które na przykład dla kompilatora GCC definiuje się następująco:
#define reg(a) __asm(#a)
W przypadku, gdy kompilujemy kod adresujący zmienne globalne względem rejestru bazowego (opcja DATA=NEAR w SAS-ie, czy -fbaserel w GCC) pojawia się dodatkowy problem. Program przed dojściem do dispatchera "przechodzi" przez kod systemu i MUI, który może zmienić zawartość rejestru bazowego (przeważnie jest to rejestr a4). Stąd wymagany modyfikator "__saveds" który wymusza przywrócenie prawidłowej wartości rejestru bazowego. W zasadzie nie byłoby to konieczne gdybyśmy w dispatcherze i naszych funkcjach z niego wywoływanych nie używali zmiennych globalnych. Jest to jednak dość trudne choćby dlatego, że globalne są z reguły bazy bibliotek, które są niejawnie używane przy wywołaniach funkcji systemowych.
Wnętrze dispatchera jest bardzo proste. Jeżeli wykonywaną metodą jest MUIM_Numeric_Stringify, to wywołana zostanie nasza funkcja zamiast standardowej. Pozostałe metody przekazujemy przez DoSuperMethodA() klasie nadrzędnej - jest nią klasa Slider. W ten sposób fizycznie jest zrealizowane dziedziczenie metod. Teraz pozostaje nam
Na początek kod:
long Stringify (Class *cl, Object *obj, Msg msg) { if (((struct MUIP_Numeric_Stringify*)msg)->value == 13) return (long)"--"; else return (DoSuperMethodA (cl, obj, msg)); }
Metoda może być już zwykłą funkcją z parametremi przekazywanymi przez stos, nie ma też potrzeby odtwarzania rejestru a4. Do metody program przechodzi bezpośrednio z dispatchera, nie wykonując żadnego "obcego" kodu nie wygenerowanego przez nasz kompilator. Jak wynika z dokumentacji metoda powinna zwrócić adres napisu odpowiadającego danej pozycji suwaka. Jeżeli pozycja ta wynosi 13, wtedy zwracany jest adres napisu "--". Co jednak zrobić z pozostałymi wartościami? W zasadzie wypadałoby tu zamienić liczbę na jej postać tekstową i adres tego tekstu zwrócić przy wyjściu. Ale przecież kod wykonujący to zadanie znajduje się w standardowej metodzie MUIM_Numeric_Stringify! Należy to wykorzystać. Dlatego dla pozostałych wartości po prostu... przekazuję metodę klasie nadrzędnej. Dokładniej - przekazuję tą metodę klasie Slider, a ona przekaże metodę klasie Numeric, gdzie metoda zostaje wykonana. Pokazana tu możliwość "selektywnego dziedziczenia" jest bardzo wygodna i przydatna w praktyce. Dziedziczę daną metodę po klasie nadrzędnej wtedy, gdy mi to odpowiada (w przykładzie dla wartości różnych od 13), w przeciwnym wypadku wykonuję własny kod. Oprócz tego kodu musimy się jeszcze zająć czymś takim jak
Czynności te omawiałem już w pierwszym odcinku (ACS 3/99), ale dla porządku wspomnę tu o nich jeszcze raz. Nasza klasa prywatna musi zostać stworzona zanim utworzymy jakikoilwiek jej obiekt. Usunięcie natomiast może nastąpić dopiero po usunięciu wszystkich obiektów tej klasy. Najwygodniej tworzyć klasy między otwarciem bibliotek a tworzeniem aplikacji. Oto fragment kodu tworzący naszą klasę:
struct MUI_CustomClass *SuperSliderClass; SuperSliderClass = MUI_CreateCustomClass (NULL, MUIC_Slider, NULL, 0, Dispatcher);
Jak widać zadanie to sprowadza się do wywołania MUI_CreateCustomClass() z odpowiednimi parametrami, co szczegółowo opisałem w pierwszym odcinku tego cyklu. Rozmiar struktury danych wynosi w tym przypadku zero, ponieważ nie potrzebuję rozszerzać struktury danych obiektu o żadne dodatkowe dane. Jeszcze prostsza jest likwidacja klasy:
MUI_DeleteCustomClass (SuperSliderClass);
Powyższa linijka nie wymaga chyba komentarza.
Po sprawdzeniu, że klasa jest już gotowa (zawsze należy sprawdzić, czy zwrócony przez MUI_CreateCustomClass() wskaźnik jest różny od zera) można zabrać się do tworzenia obiektów. Służy do tego funkcja NewObject() z intuition.library, co jest jedyną różnicą w porównaniu do tworzenia obiektów klas wbudowanych MUI. Oto przykład:
slider3 = NewObject (SuperSliderClass->mcc_Class, NULL, MUIA_Slider_Horiz, TRUE, MUIA_Numeric_Max, 30, MUIA_Numeric_Min, 4, TAG_END);
Ponieważ NewObject() wymaga wskaźnika na strukturę IClass a nie MUI_CustomClass, wyciągamy do z tej drugiej struktury. Oczywiste jest, że tworząc obiekt możemy podawać mu atrybuty klas nadrzędnych. Dzięki temu dowolny suwak możemy wyposażyć w cechę wyróżniania liczby 13. Pokazuje to program przykładowy - widzimy tam dwa nasze suwaki, poziomy i pionowy.
Tak więc cel uzyskany i mogłoby się zdawać, że wszystko jest pięknie. Niestety na amatorów produkcji nietypowych suwaków czekają przykre niespodzianki. Jako ćwiczenie proponuję zmienić napis "--" na jakiś dłuższy, chociażby "Pech!". I co się okazuje? Napis na suwaku jest haniebnie obcięty! Co zrobiliśmy nie tak? Otóż okazuje się, że nic, niedoróbka tkwi niestety w MUI. Oczywiście MUI stara się ocenić wymaganą szerokość suwaka, ale robi to nieco pobieżnie. Ocena szerokości odbywa się poprzez sprawdzenie długości napisów w pikselach dla poszczególnych położeń suwaka. Ale niestety nie dla wszystkich... Klasa Slider sprawdza kilka pozycji z początku zakresu suwaka, kilka z końca i kilka położonych na środku. Przykładowo dla suwaka o zakresie 4 - 30 takim jak w przykładzie sprawdzane są długości napisów dla pozycji 4, 5, 16, 17, 19, 29 i 30. Jak więc widać nasza trzynastka jest zwyczajnie pominięta. Dlatego w przypadku takich wzbogacanych suwaków musimy się postarać, aby najdłuższe teksty umieścić w początkowych lub końcowych pozycjach zakresy suwaka. Nie jest to na pewno wygodne, ale nie pozostaje nam nic innego. Czy aby na pewno? Rozwiązanie tego problemu będzie tematem następnego odcinka kursu. Uczynię naszą klasę bardziej uniwersalną przez dodanie do niej atrybutu MUIA_SuperSlider_SpecialText, przy pomocy którego będzie można określić tekst na wyróżnionej pozycji, oraz MUIA_SuperSlider_SpecialValue określającego tę pozycję. Będzie to okazja do pokazania sposobu dodawania do klasy nowych atrybutów, pisania konstruktora, no i pokażę swoje rozwiązanie problemu obcinanych napisów. Dodatek - tutaj.