C dla każdego (cz. 24.)

Pomocy! (2)

Dziś zajmiemy się najważniejszym mechanizmem pomocy: biblioteką "amigaguide.library". Dokończymy też rozpoczęty przed miesiącem listing.

Biblioteka "amigaguide.library" pojawiła się pierwszy raz w wersji 3.0 (39) systemu operacyjnego. Udostępniona została jednak również wersja 34 tej biblioteki, pracująca pod systemami 1.3 i 2.04. Biblioteka przeszła również poważną metamorfozę w wersji 3.1 (40) systemu operacyjnego.

Można by się więc cieszyć, że biblioteka jest dostępna pod wszystkimi systemami i że była aktywnie rozwijana. Problem jednak w tym, Że każda z tych wersji jest inna! Najprymitywniejsza z nich- wersja 34 - nie oferuje żadnych specjalnych wodotrysków. Ot - tekst i gadżety. W wersji 39 dodano możliwość zmiany atrybutów tekstu - czcionek, rozmiaru, koloru itd. Pojawiły się również pewne mechanizmy automatycznego formatowania paragrafów. Wersja 40, najbardziej rozbudowana, umożliwia przede wszystkim jeszcze lepszą kontrolę nad automatycznym formatowaniem tekstu. W wersji tej zmieniono jednak znaczenie znaku "\". W poprzednich wersjach znak ten miał specjalne znaczenie tylko przed niektórymi znakami, jak np. '@'. W wersji 40 zmieniono to i np. aby na ekranie zostało wyświetlone "\n", w dokumentacji trzeba napisać "\\n". To z kolei spowoduje, że w wersjach 34 i 39 na ekranie pojawi się właśnie "\\n". Co więc robić? Dwie (a może i trzy) osobne wersje dokumentacji? A może jakieś automatyczne konwersje podczas instalacji? Horror!

Żeby na tym jednak problemy się kończyły... Pomoc tego typu powinna być dostępna na żądanie i asynchronicznie, a więc zarówno biblioteka, jak i dokumentacja powinny być otwierane dopiero przy pierwszym naciśnięciu przycisku "Help", a otwarte okno z pomocą nie powinno blokować aplikacji. Tymczasem w dokumentacji systemu znajdziemy przykłady, które blokują aplikację lub ładują ją zaraz po uruchomieniu programu. Najbardziej oczywista implementacja została po prostu przemilczana. Skandal!

No, ale skończmy biadolenie i weźmy się do pracy. W naszym listingu pomoc AmigaGuide jest wywoływana, gdy użytkownik zażąda pomocy dla menu (IDCMP_MENUHELP), dla aktywnego gadżetu tekstowego (IDCMP_GADGETUP z "Code" równym Ox5F) oraz gdy naciśnie przycisk "Help" w dowolnej innej sytuacji (IDCMP_RAWKEY z "Code" równym Ox5F). W tym ostatnim przypadku korzystamy z informacji udzielonych nam wcześniej przez IDCMP_GADGETHELP i zapamiętanych w zmiennej "ostatnigad".

We wszystkich tych przypadkach wywoływana jest funkcja "duzapomoc()", której parametrem jest liczbowy identyfikator rozdziału w dokumentacji.

Funkcja ta najpierw inicjuje pomoc AmigaGuide. Polega to na otwarciu biblioteki "amigaguide.library", a następnie wywołaniu funkcji:

APTR OpenAmigaGuideAsyncA( struct NewAmigaGuide *nag, sfcruct Tagitem *atbrs ) ;
APTR OpenAmigaGuideAsync (struct NewAmigaGuide *nag, Tag Tag1, ... ) ;

Funkcja otwiera dokument AmigaGuide, zwracając "czarną skrzynkę" - adres na prywatną strukturę, lub NULL w przypadku niepowodzenia. Tagów nie podajemy zwykle żadnych, bo nie ma specjalnie z czego wybierać. Parametr "nag" to wskaźnik na zainicjowaną strukturę "NewAmigaGuide", która jest zdefiniowana w pliku "libraries/amigaguide.h". Zawiera ona wiele pól, z których najważniejsze to:

- nag_Name: nazwa dokumentu z pomocą AmigaGuide.

- nag_Screen i nag_PubScreen: odpowiednio: adres ekranu lub jego nazwa - na nim zostanie otwarte okno pomocy.

- nag_Context: jest to jeden ze sposobów sterowania pomocą. W tym wypadku, podajemy tablicę wskaźników na napisy będące nazwami rozdziałów dokumentacji. Dzięki temu nie trzeba póˇniej podawać napisów, a tylko indeksy w tej tablicy.

Po zakończonym sukcesem wywołaniu funkcji, należy pobrać informację o porcie, przez który pomoc komunikuje się z naszą aplikacją:

ULONG AmigaGuideSignal( APTR cl );

Parametrem jest wartość zwrócona przez OpenAmigaGuideAO, a funkcja zwraca maskę bitową sygnału portu.

Teraz następuje "gwóźdź programu", a więc to, czego nigdzie nie opisano. Niby pomoc mamy już otwartą, ale okazuje się, że nie można się z nią jeszcze komunikować, bo nie została zakończona jej inicjalizacja. Przy pomocy funkcji Wait() czekamy więc, aż w porcie pojawią się jakieś wiadomości. Następnie wywołujemy funkcję "obsluzagmsg()", która wiadomości obsługuje.

Wiadomości od AmigaGuide pobieramy i zwracamy przy użyciu funkcji:

struct AmigaGuideMsg *GetAmigaGuideMsg( APTR cl ) ;
void RepiyAmigaGuideMsg ( struci AmigaGuideMsg *amsg ) ;

Parametrem pierwszej z nich jest wartość zwrócona przez OpenAmigaGuideAsyncAO. Struktura "AmigaGuideMsg" jest zdefiniowana w pliku "libraries/amigaguide. h" i jest rozszerzoną wersją struktury "Message". Najważniejszym polem tej struktury jest "agm_Type", które identyfikuje typ przybyłej wiadomości. Interesujące nas typy to:

- ActiveToolID: oznacza, że inicjalizacja pomocy zakończyła się i można się już z nią komunikować.

- ToolCmdRepłylD, ToolStatusID, ShutdownMsgID: gdy przybędzie któryś z nich, sprawdzamy czy nie wystąpił błąd, co poznajemy po niezerowej wartości pola "agm_Pri_Ret". Słowny opis błędu uzyskujemy wywołując funkcję:

STRPTR GetAmigaGuideString( long id );

Jako parametr podajemy wartość pola "agm_Sec_Ret". Po tych wszystkich machinacjach możemy wreszcie poinformować pomoc, który rozdział nas interesuje:

LONG SefcAmigaGuideContextA( APTR cl, unsigned long id, struct TagItem *attrs );
LONG SetAmigaGuideContext( APTR cl, unsigned long id, Tag tag1, ... );

Jako "cl" podajemy wartość zwróconą przez OpenAmigaGuideAsyncA(), "id" to identyfikator rozdziału, a tagów póki co żadnych nie ma. Funkcja zwraca TRUE, jeżeli operacja się powiodła.

Wywołanie powyższej funkcji nie powoduje jednak otwarcia okna na ekranie ani ustawienia właściwego rozdziału. Aby to osiągnąć, musimy jeszcze wywołać funkcję:

LONG SendAmigaGuideContextA( APTR cl, struct TagItem *attrs );
LONG SendAmigaGuideContext ( APTR cl, Tag tag1, ... );

Jako pierwszy parametr podajemy wartość zwróconą przez OpenAmigaGuideAsyncA(), tagów nie ma na razie żadnych.

Jak zwykle, przy wychodzeniu z programu należy jeszcze pomoc zamknąć. Służy do tego funkcja:

void CloseAmigaGuide( APTR cl );

Nie chce nam się kolejny raz powtarzać, co jest jej parametrem...

I to w zasadzie wszystko, jeżeli chodzi o typowe wykorzystanie "amigaguide.library". Potrzebna jest nam jeszcze oczywiście sama dokumentacja w tym standardzie. Ze względów oszczędnościowych zamieszczamy tylko jej makietę. Temat tworzenia dokumentacji AmigaGuide był już omawiany w Magazynie AMIGA 11/94 przez Wojciecha Wyparta - "Przewodnik po AmigaGuide".

Fragmenty zaprezentowanego sposobu obsługi systemu AmigaGuide bazują na znajdującym się na Aminecie w katalogu dev/c archiwum "simpleguidel.fha", autorstwa Pettera Nilsena. Pragniemy Jemu w tym momencie podziękować za zgodę na ich wykorzystanie.

@database "Pomoc.guide" @node MAIN Główne okno: @("Wpisz" link BUTTON_GAD] @ ["Gadżet tekstowy" link STRING.GAD} @("Ramka z tekstem" link TEXT_GAD} @ ( "Górne menu" link MENU} @endnode @node BUTTON_GAD Tu miejsce na opis. @endnode @node STRING_GAD @endnode @node TEXT_GAD @endnode @node MENU Górne menu: @{"Menu Projekt" link PROJEKT_MENU} @endnode @node PROJEKT.MENU Menu Projekt: @{"Otwórz..." link OTWORZ_ITEM} @{"Zapisz" link ZAPISZ_ITEM} @{"Zapisz jako..." link ZAPISZ.JAKO_ITEM} @endnode @node OTWORZ_ITEM @endnode @node ZAPISZ_ITEM @endnode @node ZAPISZ_JAKO_ITEM @endnode



/* Wstaw poniższe linie w MIEJSCE l (zawartość funkcji obsluzagmsg()). */ int bylblad=0 ; struct AmigaGuideMsg *msg; while (msg-GetAmigaGuideMsg(aginfo)) { switch (msg->agm_Type) { case ToolCmdReplyID: case ToolStatusID: case ShutdownMsgID: if (msg->agm_Pri_Ret) { printf("Blad obslugi Online help!\n%sl\n", GetAmigaGuideString(msg->agm_Sec_Ret)); bylblad=l; } break case ActiveToolID: if (aktywny) *aktywny=1 ; } ReplyAmigaGuideMsg(msg) ; } return bylblad; /* Wstaw ponizsze linie w MIEJSCE 2 (miedzy obsluzagmsg () i main () ) */ void duzapomoc(int node} { static const char* nazwy[]= { "MAIN", "BUTTON_GAD". "STRING_GAD", "TEXT_GAD". "MENU" , "PROJEKT_MENU", "OTWORZ_ITEM", "ZAPISZ_ITEM", "ZAPISZ_JAKO_ITEM" ); if (!aginfo) { int aktywny=0; static struct NewAmigaGuide nag; if (!AmigaGuideBase) if (!(AmigaGuideBase=OpenLibrary("amigaguide.library", v34+!\n"); return { printf("Online help wymaga biblioteki "amigaguide.library\" v34+'\n") ; return } nag.nag_Name="Pomoc.guide" ; nag.nag_Screen=pubscr; nag.nag_Context=(STRPTR*)nazwy; if (!(aginfo=OpenAmigaGuideAsyncA(&nag, 0))) { printf("Blad podczas inicjowania online help!\n"); return; } agsig=AmigaGuideSignal(aginfo) ; Wait (agsig); while (!aktywny) if (obsluzagmsg(&aktywny)} { CloseAmigaGuide(aginfo) ; aginfo=0 ; return; } } SetAmigaGuideContext(aginfo, node, NULL) ; SendAmigaGuideContext(aginfo, NULL) ; } /* Wstaw ponizsze linie w MIEJSCE 3 (w instrukcji switch, po case IDCMP_GADGETHELP). */ Case IDCMP_MENUHELP: ( int node=POMOC_MENU; struct Menultem *item=ItemAddress(menu, kopia_msg.Code); if (item && GTMENUITEM_USERDATA(item)) node=(int)GTMENUITEM_USERDATA(item) ; else if (MENUNUM(kopia_msg.Code)==0) node=POMOC_PROJEKT; duzapomoc(node) ; break; } case IDCMP_RAWKEY: if (kopia_msg.Code==Ox5F) /* Help */ duzapomoc(ostatni gad ? ( (struct PomocDoGadzetu*)ostatnigad->UserData)->pdg"Node : POMOC_MAIN) ; break; case IDCMP_GADGETUP: if (kopia.msg.IAddress==tabgad[STRING_ID]&&kopia_msg.Code==Ox5F) duzapomoc(((struct PomocDoGadzetu*)tabgad[STRING_ID]->UserData)->pdg_Node); break;