W ostatnim odcinku obiecałem co prawda dalsze męczenie suwaków i zlikwidowanie problemu zjadanych napisów, ale okazało się, że... nie da się tego problemu w prosty sposób rozwiązać. Jak widać MUI nie jest idealne i czasem możemy napotkać na problemy trudne do pokonania. Ostatnio w kursach programowania panuje moda na polecanie muzyki do słuchania. Ja nic Wam drodzy czytelnicy nie polecę, bo jakbyście się dowiedzieli czego lubię słuchać, to przestalibyście czytać ten artykuł :-). Tak więc w ciszy i skupieniu przejdźmy do rzeczy.
Będzie to dosłownie i w przenośni gadżet, ponieważ nie ma wielkiego praktycznego zastosowania, ale zabawnie działa. Proponuję popatrzeć na obrazek przedstawiający okno naszego dzisiejszego przykładu. Widzimy tam cztery gadżety ze "szpilkami" których główki podążają za wskaźnikiem myszy. Wskaźnika niestety nie widać, bo nie dał się "zgrabić", ale jak zwykle program przykładowy wraz z pełnym kodem źródłowym znajduje się na okładkowym CD, zachęcam do pobawienia się nim trochę. Klasa gadżetu nazwana "MouseArrow" została wyprowadzona z klasy Area, która to jest nadrzędną klasą dla każdego obiektu widocznego w okienku aplikacji MUI. Funkcję tworząca klasę omówiłem szczegółowo w poprzednim odcinku. Nowością jest to, że napisaliśmy aż pięć nowych metod. Wszystkie te metody są metodami klasy Area. Zacznijmy od
MUIM_ASKMINMAX
Tą metodę MUI wywołuje jeżeli chce się "dowiedzieć" jakie mają być rozmiary obiektu. W wiadomości metody dostajemy wskaźnik na strukturę MUI_MinMax, do pól której dodajemy rozmiary naszego obiektu. Dlaczego dodajemy? Na początku przekazujemy metodę do klasy nadrzędnej, która wpisuje do struktury grubość ramek, odstępy między ramkami a zawartością obiektu itp. My do tego dokładamy rozmiary wnętrza. W strukturze mamy trzy pary rozmiarów: minimalny, domyślny i maksymalny. Rozmiar minimalny to taki, poniżej którego MUI nigdy nie zejdzie przy skalowaniu okna. Podobnie rozmiar maksymalny ogranicza nam zwiększanie się obiektu przy powiększaniu okna. Jeżeli nie ograniczamy rozmiarów maksymalnych dodajemy do odpowiedniego pola struktury stałą MUI_MAXMAX. Rozmiar domyślny zostanie użyty przy otwieraniu okna, jeżeli MUI nie napotka na żadne ograniczenia typu "okno nie mieści się na ekranie". W przykładzie wszystkie trzy rozmiary są sobie równe, co oznacza ustawienie rozmiarów obiektu na sztywno. Nie jest to najlepszy pomysł, ale uprości nam później obliczenia przy rysowaniu szpilek. W metodzie tej mamy dostęp do szeregu danych ułatwiających obliczenie rozmiarów obiektu. Oto wykaz:
_dri (obj) - struktura DrawInfo ekranu na którym zostanie otwarte okno programu (w chwili wykonywania metody AskMinMax okna jeszcze nie ma!). Stąd możemy zaczerpnąć informacje na temat proporcji pikseli ekranu, jego głębi kolorów i o kolorach systemowych, oraz domyślnej czcionce.
_screen (obj) - struktura Screen ekranu, a więc bitmapa, viewport i inne rzadziej przydatne informacje, najczęściej DrawInfo wystarczy.
_font (obj) - czcionka obiektu (nie mylić z czcionką ekranu!), często przydaje się do obliczeń, jeżeli w naszym obiekcie będą jakieś napisy.
Jak łatwo zgadnąć w tej metodzie rysujemy wnętrze obiektu. Zasadą w MUI jest, że poza metodą MUIM_Draw nie mamy prawa postawić ani jednego piksela w RastPorcie okna! Jest to na pewno nowość dla kogoś, kto wcześniej korzystał z Intuition, GadTools, czy gadżetów BOOPSI. Początkowo może to się wydawać ograniczeniem, ale ułatwia zachowanie porządku w oknie. Pierwszym krokiem jaki musimy wykonać jest przekazanie metody klasie nadrzędnej, która narysuje ramki obiektu i jego tło. Teraz możemy już bazgrać, ale tylko we wnętrzu naszego obiektu. Pozycję tego wnętrza określają cztery makra: _mtop (obj), _mbottom (obj), _mleft (obj) i _mrigth (obj), określające odpowiednio górną, dolną, lewą i prawą krawędź wnętrza obiektu. Pole do popisu mamy tylko wewnątrz tak określonego prostokąta. Współrzędne odniesione są do górnego lewego rogu okna i tak jak w RastPorcie rosną od zera w prawo i w dół. Możemy więc je podawać bezpośrednio funkcjom rysującym na RastPorcie okna. Adres samego RastPortu podaje makro _rp (obj). W przykładzie korzystając z danych pobranych ze struktury obiektu, obliczam początek i koniec szpilki. Dane te to odległość w pionie i w poziomie miedzy wskaźnikiem myszy a środkiem gadżetu. Są one obliczane w metodzie MUIM_HandleEvent, o której dalej. Na podstawie tychże odległości i długości szpilki (stała RADIUS to połowa długości), oraz patentów panów Pitagorasa i Talesa obliczam położenia końców szpilki względem jej środka. Narysowanie kreski jest już trywialne, a pięć pikseli na jednym z jej końców wieńczy dzieło. Jak zauważyliście, beztrosko zmieniam sobie aktualny kolor RastPortu funkcją SetAPen (). W tym przypadku mogę nie przywracać pierwotnej wartości przy wyjściu z metody. Jednak na przykład z trybem rysowania (SetDrMd ()) nie można już sobie tak beztrosko poczynać, jeżeli np. zostawimy na wyjściu z metody tryb COMPLEMENT albo INVERSVID, to możemy się lekko zdziwić po uruchomieniu programu...
Te dwie metody pozwalają na przygotowanie obiektu do pracy, a później zrobienie porządków. Nie wszystko można zrobić w konstruktorze obiektu (metoda OM_NEW), choćby z tego względu, że w czasie konstrukcji część obiektów programu jeszcze nie istnieje, nie mamy też dostępu do wszystkich niezbędnych informacji, zwłaszcza dotyczących ekranu, na którym przyjdzie nam działać. Jednym z zadań, do których wykorzystuje się te metody, jest włączenie reakcji obiektu na poczynania użytkownika - odpowiada to ustawieniu flag IDCMP w Intuition. Jeżeli chcemy, aby obiekty naszej klasy reagowały na określone komunikaty IDCMP (mysz, klawiatura, włożenie dyskietki itp.), powinniśmy właśnie w metodzie MUIM_Setup dodać do okna obiektu tak zwany EventHandler (nie można tego zrobić w konstruktorze - wtedy okno obiektu jeszcze nie istnieje!). W tym celu w strukturze danych obiektu (struct MouseArrow w przykładzie) umieściłem strukturę MUI_EventHandlerNode. Zwracam uwagę - samą strukturę, a nie wskaźnik. Teraz należy ją odpowiednio wypełnić. Pole ehn_Pri to priorytet naszego handlera. W większości przypadków można zostawić tu zero, jeżeli nasz obiekt będzie otrzymywał dużo komunikatów i większość z nich będzie kasował (patrz niżej) można ten priorytet zwiększyć. Pole ehn_Flags po prostu zerujemy, w pola ehn_Object i ehn_Class wpisujemy nasz obiekt i jego klasę. W polu ehn_Events umieszczamy wszystkie flagi IDCMP na jakie będziemy czyhać. Wypełnioną strukturę dołączamy do obiektu okna metodą MUIM_Window_AddEventHandler. Odłączenie handlera w metodzie MUIM_Cleanup nie wymaga chyba komentarza. Jeżeli w trakcie działania programu chcemy zmienić zestaw aktywnych flag IDCMP, nie możemy ograniczyć się do zmiany pola ehn_Events - to nic nie da. Należy odłączyć handler od okna, potem zmienić flagi w strukturze a następnie dołączyć handler z powrotem.
W metodzie MUIM_Setup ważne jest, aby na samym początku przekazać ją klasie nadrzędnej i sprawdzić wynik. Jeżeli klasa nadrzędna zwróci TRUE, wtedy robimy co nasze i jeżeli się powiedzie, również zwracamy TRUE. Jeżeli "z góry" dostaniemy FALSE, albo nasza inicjalizacja się nie powiedzie, zwracamy FALSE. Przy MUIM_Cleanup najpierw sprzątamy nasze śmieci, a dopiero na końcu przekazujemy metodę klasie nadrzędnej, zwracana wartość nie ma znaczenia.
Metoda ta zostanie wywołana w momencie, gdy EventHandler otrzyma od Intuition wiadomość IDCMP jednej z klas oczekiwanych przez nas. Nasza metoda może ją otrzymać na dwa sposoby. Po pierwsze dostaniemy wspomnianą wiadomość Intuition do obróbki (pole 'imsg' wiadomości metody). Po drugie jeżeli jest to wiadomość "z klawiatury" i kombinacja klawiszy odpowiada jednej ze zdefiniowanych w preferencjach kombinacji MUI, dostaniemy numer tej kombinacji (pole 'muikey' wiadomości metody). W naszym przykładzie nie żądaliśmy żadnych wiadomości z klawiatury (tylko IDCMP_MOUSEMOVE), więc sprawdzanie 'muikey' możemy sobie darować. Z przekazanej struktury IntuiMessage wyciągamy interesujące dane i przetwarzamy je. W przykładzie po sprawdzeniu, że 'imsg' istnieje i że jest klasy IDCMP_MOUSEMOVE, odczytujemy współrzędne myszy i przekształcamy je na odległości od środka gadżetu. Możemy spokojnie skorzystać ze wspomnianych wcześniej makr podających pozycję naszego gadżetu w oknie. Są one ważne tylko, gdy okno jest otwarte (np. ikonifikacja programu unieważnia je), ale gdyby okno było zamknięte, nie dostawalibyśmy wiadomości od Intuition... Gotowe wartości wstawiamy do struktury obiektu, będą potrzebne przy rysowaniu, w metodzie MUIM_Draw... A właśnie, teraz należałoby odrysować obiekt na nowo - szpilka się obróci! Można oczywiście ręcznie wywołać metodę MUIM_Draw, ale prościej będzie wykonać na naszym obiekcie funkcję MUI_Redraw z muimaster.library. Dzięki temu przy każdym ruchu myszką obiekt będzie odrysowany. Wyjasnienia wymaga jeszcze tajemnicza stała MADF_DRAWOBJECT. Służy ona do sterowania odrysowaniem obiektu. MADF_DRAWOBJECT oznacza kompletne przerysowanie, łącznie z ramkami i tłem. MADF_DRAWUPDATE natomiast powoduje, że klasa Area nie rysuje na nowo ramek ani tła. Przy naszej klasie podanie tej drugiej wartości spowoduje, że szpilki nie będą kasowane - zachęcam do eksperymentów. Przy bardziej rozbudowanej grafice często w metodzie MUIM_Draw sprawdza się wartość tej flagi (jest ona umieszczana w wiadomości metody MUIM_Draw) i ogranicza się dzięki temu ilość rysowanej grafiki, pokażę to w jednym z następnych odcinków.
Wróćmy jednak do MUIM_HandleEvent. Przy wychodzeniu z metody możemy otrzymaną wiadomość przekazać do dalszej obróbki przez MUI (czyli przekazania jej pozostałym EventHandlerom), albo "zjeść" ją, wtedy jej przetwarzanie się kończy. Kontrolujemy to wartością zwracaną przez metodę - zero oznacza pozostawienie wiadomości, stała MUI_EventHandlerRC_Eat "zjedzenie" wiadomości. Kiedy usunąć wiadomość, a kiedy przekazać ją dalej? Jeżeli ustalimy ponad wszelką wątpliwość, że dana wiadomość dotyczy tylko naszego obiektu, wtedy najepiej wiadomość usunąć - skrócimy w ten sposób czas jej przetwarzania. Na przykład kliknięcie myszką na gadżet - jeżeli otrzymamy wiadomość IDCMP_MOUSEBUTTONS a analiza wiadomości pokaże, że wciśnięto lewy przycisk i wskaźnik myszy znajdował się nad gadżetem, wtedy wiemy, że była to wiadomość istotna tylko dla naszego obiektu - zatem usuwamy ją. W programie przykładowym nie możemy tego zrobić - korzystamy z wiadomości na obszarze całego ekranu, a wiadomość jest wykorzystywana przez cztery obiekty. Spróbujcie zmienić zwracane przez metodę 0 na MUI_EventHandlerRC_Eat. Okaże się że na poruszenia myszą zareaguje tylko jeden obiekt. Przy decydowaniu o usuwaniu wiadomości pomocna będzie (zwłaszcza przy obsłudze klawiatury) znajomość kolejności wywoływania EventHandlerów. Najpierw jest wywoływany EventHandler aktywnego obiektu, następnie EventHandler obiektu domyślnego (ustawianego atrybutem MUIA_Window_DefaultObject), następnie pozostałe obiekty według priorytetów. Oczywiście "zjedzenie" wiadomości przez którykolwiek EventHandler powoduje przerwanie przetwarzania.
Na tym dziś zakończę. Oczywiście nie opisałem żadnej z metod wyczerpująco, ale z praktyki wiem, że pisanie o konkretnych przykładach jest skuteczniejsze niż suchy teoretyczny wykład. Na pewno więc jeszcze nie raz do zdobytej dziś wiedzy wrócimy rozszerzając ją o nowe zagadnienia. Dodatek - tutaj.