\begindata{text,538489940} \textdsversion{12} \template{default} \define{global } \define{itemize } \formatnote{\chapter{Example 17: Creating a view that has children}} \indexi{View++With children} \indexi{Children} \indexi{Child views} \indexi{Managing children} \indexi{Insets++Child} The perspective on the Andrew Toolkit interaction up to now has been from that of the child view: Examples 1-16 illustrated how to build a class that would respond appropriately if included as a child in other views. This example shifts to the parent's perspective: It illustrates how to build a view that includes a child. In addition, this example illustrates a principle that guided much of the Andrew system development: When application programmers are implementing new views, they should use views that already exist as building blocks for their own views whenever possible. For example, the Andrew mail program uses textview/text inset as a building block. The mail view builds on top of the textview/text code without changing textview/text at all, but simply adds new commands to implement functionality needed for mail. Example 17 will also use a text inset as its child view. Of course, a view's child can also be a completely new view. Using an already existing view simply saves effort. Example 17 is similar in functionality to Example 16. Instead of displaying the string, ``hello world,'' however, it displays a textview with a text data object that contains ``hello world.'' The user interface is the same, with the exception that mouse hits inside of the textview go to the textview, not directly to helloworld, so the user must click outside of the textview rectangle to drag the \italic{helloworld} string. The example illustrates the following activities: \itemize{ Creating a child view at the appropriate time Allocating screen space for a child Inserting the child into the view tree Passing requests (e.g., mouse hits) to your child Passing your child's requests (e.g., update, input focus) up the view tree } The discussion that follows presents a step-by-step description of how to modify the \italic{helloworld} class in Example 16 to produce Example 17. If you were to follow the steps, you would produce a program, called \italic{helloworldapp}, in seven files: \itemize{ a hello.ch file -- will contain the class definition for the \italic{helloworld }data object. We will override the InitializeObject and FinalizeObject classprocedures. a hello.c file -- will contain statements that import Andrew Toolkit classes used by the data object, initialize the class, and define the Read and Write methods. We will add much of the style code from helloa.c, override the InitializeObject and FinalizeObject class procedures, and modify the Write method. a hellov.ch file -- will contain the class definition for the \italic{helloworld} view. We will override the SetDataObject and the LinkTree methods, and add a few new data structures. a hellov.c file -- will contain statements that import Andrew Toolkit classes used by the view and definitions of the methods. We will be modifying many of the method definitions to convert helloworldview from a child object to a parent object. a helloa.ch file -- will contain the class definition for the application program that will display an instance of the class in a window. This will be exactly the same as in Example 16. a helloa.c -- will contain declarations needed by the Andrew Toolkit linking and loading facilities as well the definition of the \italic{helloworldview} application method. This will be exactly the same as in Example 16. Makefile -- will contain the directions for compiling, linking and loading. This will be exactly the same as Example 16. } For a complete listing of these files, see \italic{Program Listing for Example 17 } at the end of this section on p. \begindata{textref,539236104} \textdsversion{12} # ExSeventeenListing\ \enddata{textref,539236104} \view{textrefv,539236104,951,0,0}. The source code is available in the directory /usr/andrew/examples/ex17, together with the compiled program. Although the discussion refers directly to this example, which illustrates a view with a single child, the information applies generally to the creating of any view with any number of children. \formatnote{\bold{Action 1.} \bold{command }prompt type \formatnote{To run the program, at the runapp /usr/andrew/examples/atk/ex17/helloa and press the Enter key. } \bold{Response.}\formatnote{ }The program will produce two windows, each split into two lpairs as shown in the figure on the previous page. The left lpairs have both left and bottom scroll bars, and contain another window (a child text view displaying \bold{hello}\italic{world}) with a scroll bar. The right lpair contains a text view displaying \bold{hello}\italic{world} and has only the left scroll bar. \bold{Action 2.} \formatnote{ of the left lpairs. \bold{Response. } \formatnote{ location of the mouse hit. }Click with the left mouse button in one }The child text view will move to the \bold{Action 3.}\formatnote{ button pressed down. }Drag the mouse with the right mouse \bold{Response. }\formatnote{ dragged around the lpair window. }The child text view window will be \bold{Action 4.}\formatnote{ cards. }Press both mouse buttons to see the menu \bold{Response.} \formatnote{ }Two menu cards, one with \bold{Quit} and the other with \bold{Hello World} will appear. The Hello World functions (i.e. Relocate, Write, Read, Invert, Center) are exactly the same as in the previous examples. \bold{Action 5.}\formatnote{ }Click with a mouse button inside the child text view, then pop up the menus. \bold{Response.} \formatnote{ }The text menu cards will appear, since it is now the child text view that has the input focus. \bold{Action 6.}\formatnote{ }The rest of the program has the same functionality as in previous examples. To quit the program, choose Quit from the menu cards. \bold{Response.} \formatnote{ screen. }Both windows will disappear from the }\ \begindata{bp,538928712} \enddata{bp,538928712} \view{bpv,538928712,952,0,0} \section{Modifying the class definition} \subsection{Creating a view from component views} \indexi{Views++Creating} \indexi{View++Building blocks} You should consider creating a new view from existing building blocks whenever part of your application's user interface shares significant similarities to an existing view's user interface. For example, the user interface for the body of a mail message is similar to the user interface provided by \italic{textview}, so it makes sense to build the body of a mail message from \italic{textview}. A new view that you build from an existing component can override the component's user interface behavior, so the user interface can differ somewhat from the component's. A rule of thumb for deciding how different the two can be is the following: If you are considering creating a new view from an existing building block, but find yourself needing to make changes to large numbers of the building block's methods, you will probably be better off implementing a new view altogether; on the other hand, if there are large numbers of methods that you can use as is, or you can use by simply adding new methods, the building block approach will save you effort. \subsection{Defining the data object} \indexi{Data objects++Defining} If you are building a view that is going to have a child views and the children have associated data objects, you must declare the \italic{dataobject} class procedures \italic{InitializeObject} and \italic{FinalizeObject.} These class procedures are the places where you will create the child views' data objects and delete them, respectively. You must also modify the \italic{dataobject}'s data definition to add a pointers to the child views' data objects. For example, the following is the new class declaration for the example dataobject, \italic{helloworld}: \formatnote{ class helloworld[hello]: dataobject[dataobj] \{ overrides: Read(FILE *file,long id) returns long; Write(FILE *file,long writeId,int level) returns long; \bold{classprocedures: }InitializeObject(struct helloworld *hw) returns boolean;\bold{ FinalizeObject(struct helloworld *hw);} data: long x,y; boolean blackOnWhite; \bold{struct dataobject *dobj;} \}; } In previous examples, to display ``hello world'' we used a constant string that was cheap to generate, so we did not need to explicitly store it. But in this example, to display "hello world" we will use a child view, in this case a \italic{textview} whose data object is a \italic{text}. The statement \italic{ struct dataobject *dobj;} declares a pointer to it. In previous examples, \italic{FinalizeObject} was not declared, since it had no work to do. In this example, it will free the space allocated to the child view's data object, \italic{text}. \subsection{Creating the children's data objects} \indexi{Data objects++Creating children} If you are creating a new view with children, you must create the children's data objects and keep pointers to them in the new view's associated data object. You should also initialize the children's data objects. These activities should be done in the associated dataobject's \italic{InitializeObject} class procedure. For example, the following is the \italic{InitializeObject} procedure for \italic{helloworld}: \formatnote{ boolean helloworld__InitializeObject(classID,hw) struct classheader *classID; struct helloworld *hw; \{ hw->x = POSUNDEF; hw->y = POSUNDEF; hw->blackOnWhite = TRUE; hw->dobj=createInitialDobj(); return TRUE; \} struct dataobject *createInitialDobj() \{ struct text *text=text_New(); struct style *bold=style_New(),*italic=style_New(); style_SetName(bold,"bold"); style_AddNewFontFace(bold,fontdesc_Bold); style_SetName(italic,"italic"); style_AddNewFontFace(italic,fontdesc_Italic); text_InsertCharacters(text,0,"Hello world!",sizeof("Hello world!")1); text_AddStyle(text,0,5,bold); text_AddStyle(text,6,5,italic); return (struct dataobject *)text; \} } In this example, the \italic{helloworldview} will have a single child, a \italic{textview}. The statement \italic{hw->text=text_New()} in the createInitialDobj procedure creates an instance of \italic{textview}'s data object, \italic{text}, and stores a pointer to it in \italic{hw->text}. The remaining statements, \italic{text_InsertCharacters} and \italic{text_AddStyle}, initialize the child data object. It is analogous to the use with text objects previously. \subsection{Deleting the children's data objects} \indexi{Data objects++Deleting children} When a parent object with children is deleted, it should free up the children's storage, provided of course that there are no other pointers to the children. The freeing of storage should be done in the parent's \italic{FinalizeObject} class procedure by calls to the children's \italic{Destroy} or \italic{Finalize} procedures. For example, the following is helloworld's FinalizeObject procedure: \formatnote{ void helloworld__FinalizeObject(classID,hw) struct classheader *classID; struct helloworld *hw; \{ dataobject_Destroy(hw->dobj); \} } The statement \italic{dataobject_Destroy(hw->dobj)} frees the storage allocated to the text data object pointed to by \italic{hw->dobj}. \subsection{Writing a data object with children to a file} \indexi{Data objects++Writing children} The \italic{Write} method must write the contents of the data object to a file. When a view/data object is constructed from component views and data objects, the parent data object's Write method must call the children's Write methods. For example, the following is Example 17's \italic{Write} method: \formatnote{ long helloworld__Write(hw,file,writeId,level) struct helloworld *hw; FILE *file; long writeId; int level; \{ if(writeId!=helloworld_GetWriteID(hw))\{ /* only write a given version once */ helloworld_SetWriteID(hw,writeId); fprintf(file,"\\\\begindata\{%s,%d\}\\n", class_GetTypeName(hw), helloworld_UniqueID(hw)); fprintf(file,"%d %d %d\\n",hw->x,hw->y,hw->blackOnWhite); \bold{dataobject_Write(hw->dobj,file,writeId,level);} fprintf(file,"\\\\enddata\{%s,%d\}\\n", class_GetTypeName(hw), helloworld_UniqueID(hw)); \} return helloworld_UniqueID(hw); \} } The \italic{Write} method is the same as Example 11, with the exception of the statement \italic{dataobject_Write(hw->dobj,file,writeId,level)}. Like before, the method writes out the position and color (black or white) of the helloworld dataobject, \italic{hw}, but the statement \italic{dataobject_Write} writes out the text data object, \italic{hw->dobj}, as well--inside the helloworld dataobject. Recall that the \italic{level} parameter in the \italic{Write} method indicates whether or not the "top level" object is being written (i.e., not a sub-object within another object in the datastream object hierarchy). \subsection{Reading a data object with children from a file} \indexi{Data objects++Reading children} The \italic{Read} method for a data object with children is similar to the \italic{Read} methods already discussed, but the method must also read in any embedded data objects. To read embedded objects, the method must scan for the \\begindata header that the object should have written when its \italic{Write} routine was called. If the header exists, then calling the \italic{Read} method for the object will read the object, up to and including its \\enddata marker. After the embedded object returns success, the parent's \italic{Read} method should read any other data, including its own \\enddata marker. For example, the following is Example 17's \italic{Read} method: \formatnote{ long helloworld__Read(hw,file,id) struct helloworld *hw; FILE *file; long id; \{ char buf[100],classNameBuf[100]; long retVal,dobjObjId; helloworld_SetID(hw,helloworld_UniqueID(hw)); if(fgets(buf,sizeof(buf),file)==NULL || /* the %hd tells scanf that blackOnWhite is a short, not an int*/ sscanf(buf,"%d %d %hd\\n",&hw->x, &hw->y,&hw->blackOnWhite)<3|| fgets(buf,sizeof(buf),file)==NULL || sscanf(buf,"\\\\begindata\{%[^,],%d\}\\n", classNameBuf,&dobjObjId) <2) retVal=dataobject_PREMATUREEOF; else\{ retVal=dataobject_Read(hw->dobj,file,id); if(retVal==dataobject_NOREADERROR) if(fgets(buf,sizeof(buf),file)==NULL) /* read the \\enddata\{...\}*/ retVal=dataobject_MISSINGENDDATAMARKER; \} return retVal; \} } This method is similar to previous \italic{Read} methods, but after reading in the \italic{x}, \italic{y}, & \italic{blackOnWhite} values correctly, the method attempts to read in the embedded text object. The statement \italic{sscanf(buf,"\\\\begindata\{%[^,],%d\}\\n",classNameBuf,&textObjId )<2)} scans for the \\begindata header, returning \italic{dataobject_\smaller{PREMATUREEOF}} upon an error. If it is of type dataobject, the statement \italic{retVal=dataobject_Read(hw>dobj,file,id)} reads the data object (in this case, text) up to an including its \\enddata marker. If the dataobject_Read was successful (i.e., \italic{if (retVal==dataobject_\smaller{NOREADERROR}) }succeeds) the statement \italic{if(fgets(buf,sizeof(buf),file)==NULL)} performs a quick "check" for an \\enddata marker, returning \italic{dataobject_\smaller{MISSINGENDDATAMARKER}} upon a failure, success otherwise. \subsection{Defining the view} \indexi{View++Defining} If you are building a view that is going to have a child views, you must override the \italic{view} methods \italic{LinkTree} and \italic{SetDataObject}. The LinkTree method must link your child view's into the view tree; the SetDataObject method must associate the child views with their respective data objects. You must also modify the \italic{view}'s data definition to add appropriate pointers to the child views. For example, the following are the new elements in the class declaration for the example view, \italic{helloworldview}: \formatnote{ class helloworldview[hellov]: view \{ overrides: \bold{SetDataObject(struct helloworld *hw);} FullUpdate(enum view_UpdateType type, long left, long top, long width, long right); Update(); Hit (enum view_MouseAction action, long x, long y, long numberOfClicks) returns struct view *; ReceiveInputFocus(); LoseInputFocus(); GetInterface(int type) returns char *; GetApplicationLayer() returns struct view *; DeleteApplicationLayer(struct view *); \bold{LinkTree(struct view *parent);} classprocedures: InitializeClass() returns boolean; data: struct keystate *keystate; struct menulist *menulist; boolean HaveDownTransition; boolean haveInputFocus; long hitX,hitY; int x,y; boolean blackOnWhite; long frameX, frameY; long newFrameX, newFrameY; int vrWidth,vrHeight; \bold{struct view *applayer; struct view *view;} \}; } The helloworld view must now have a \italic{view} to display the text data object. Since we would like a scrollable view (in this case, a textview), we also keep a pointer to the view's application layer, \italic{applayer}. \subsection{Creating the children's views} \indexi{Views++Creating children} If you are creating a new view with children, you must create the children's views. This activity should be done in the parent view's \italic{InitializeObject} class procedure. For example, the following is the \italic{InitializeObject} procedure for \italic{helloworldview}: \formatnote{ boolean helloworldview__InitializeObject(classID,hwv) struct classheader *classID; struct helloworldview *hwv; \{ hwv->haveInputFocus=FALSE; hwv->HaveDownTransition=FALSE; hwv->keystate=keystate_Create(hwv,helloworldviewKeymap); hwv->menulist=menulist_DuplicateML(helloworldviewMenulist,hwv); hwv->newFrameX=0; hwv->newFrameY=0; \bold{ hwv->view=NULL; hwv->applayer=NULL;} return TRUE; \} } \subsection{Deleting the children's views} \indexi{Views++Deleting children} When a parent view with children is deleted, it should free up the children's storage, provided of course that there are no other pointers to the children. The freeing of storage should be done in the parent's \italic{FinalizeObject} class procedure by calls to the children's \italic{Destroy} or \italic{Finalize} procedures. For example, the following is \italic{helloworldview}'s FinalizeObject procedure: \formatnote{ void helloworldview__FinalizeObject(classID,hwv) struct classheader *classID; struct helloworldview *hwv; \{ view_DeleteApplicationLayer(hwv->view,hwv->applayer); view_Destroy(hwv->view); \} } The statement \italic{view_DeleteApplicationLayer(hwv->view,hwv>applayer)} frees the storage allocated for \italic{hwv->applayer}. The statement \italic{view_Destroy(hwv->view)} frees the storage allocated for \italic{hwv->view}. \subsection{Inserting the children into the view tree} \indexi{View tree++Children} \indexi{ \italic{LinkTree}} When building a new view out of other component views, your new view must insert the child views in the view tree, and allocate screen space to these child views. The insertion of child views under a parent view in the view tree is done with the view method, \italic{LinkTree}. You must override the method and write a method that calls \italic{LinkTree} for each of its children, and then calls \italic{super_LinkTree}. For example, for \italic{helloworldview}: \formatnote{ void helloworldview__LinkTree(hwv,parent) struct helloworldview *hwv; struct view *parent; \{ if(hwv->applayer!=NULL) view_LinkTree(hwv->applayer,hwv); super_LinkTree(hwv,parent); \} } The statement \italic{view_LinkTree(hwv->view, hwv)} links \italic{hwv}'s application layer to the view. The statement \italic{super_LinkTree(hwv,parent}) links \italic{hwv} into the view tree. The example program described here links the child into the view tree and leaves it. For applications that require dynamic view hierarchies, the Andrew Toolkit provides a method for taking children out of the view tree -\italic{view_UnlinkTree} (see View, Vol. II). \subsection{Associating the child views with their data objects} \indexi{Data objects++Child views} \indexi{Views++Child data objects} When building a new view out of other component views, your new view must associate the child views with their data objects. The association of the child views with their data objects must be done with the view method, \italic{SetDataObject}. You must override the method and write a method that calls \italic{SetDataObject} for each of its children, and then calls \italic{super_SetDataObject}. For example, for \italic{helloworldview}: \formatnote{ void helloworldview__SetDataObject(hwv,hw) struct helloworldview *hwv; struct helloworld *hw; \{ hwv->x=hw->x; hwv->y=hw->y; hwv->blackOnWhite=hw->blackOnWhite; hwv->view=(struct view*) >dobj)); class_NewObject(dataobject_ViewName(hw- hwv->applayer=view_GetApplicationLayer(hwv->view); view_SetDataObject(hwv->view,hw->dobj); super_SetDataObject(hwv,hw); \} } The first three statements initialize view data from the data object. (Such initializations cannot be done in the view's \italic{InitializeObject} method, since the view does not have a handle on its data object there.) The next statement creates a new object and assignes it a default view based on the data. Then an application layer is found for the view. The statement \italic{view_SetDataObject(hwv->view,hw->dobj)} associates \italic{hwv}'s child, \italic{hwv->view}, with its dataobject, \italic{hw->dobj}. The statement \italic{super_SetDataObject(hwv,hw)} associates \italic{hwv} itself with its dataobject, \italic{hw}. \subsection{Writing the method for full update requests} \indexi{Full update} When building a new view out of child views, your new view must call the FullUpdate method for all its children within its FullUpdate method. Before calling a child's FullUpdate method, the parent should: \itemize{ optionally negotiate with the child about its desired size (see \italic{view_DesiredSize}, View, Vol. II) allocate space to the child view with \italic{view_InsertView} } You have the option to initiate a negotiation with the child how much size it wants, given a particular space offer. In this call the child view's \italic{view_DesiredSize} method, giving it a certain amount of space, and requesting that it tell you how it actually needs. \indexi{Size negotiation} view about case you can an offer of much space \indexi{View++Size} \indexi{ \italic{DesiredSize}} A view's \italic{DesiredSize} procedure can be called anytime after view_LinkTree has been called on the view. This means that the view's parent pointer points to the actual view's parent. This enables the view to discover what type of display it will be displayed upon, and to make calls that return font dimensional information. However, the view's space allocation has not yet been set, or more precisely, may change again. The \italic{view_DesiredSize} procedure is an optional procedure and if no such procedure is provided by the view's author, the default action is for the view to claim all of the screen space it is offered. Note that a parent view does not have a responsibility to call a child view's \italic{DesiredSize}: the parent view may already know how much screen space to allocate to the child based upon the parent's own dimensions, for example. This is the case in this example program. To allocate space to a child view, you call view_InsertView which sets the child's local bounds rectangle and its visible rectangle. The local bounds rectangle defines the space allocated to the child view, while the visible rectangle indicates how much of this space is actually visible on the screen (assuming this can be expressed as a single rectangle). Normally you would make this call at the end of a size negotiation and before calling the child's \italic{FullUpdate} method. Once a child's \italic{DesiredSize} method is optionally called, and the child's \italic{InsertView} and \italic{FullUpdate} methods invoked, the child view is essentially "born," that is, the child will take its place, if any, on the screen display. The following is the \italic{FullUpdate} method for \italic{helloworldview}: \formatnote{ #define WIDTH 100 #define HEIGHT 100 void helloworldview__FullUpdate(hwv,type,left,top,width,height) struct helloworldview *hwv; enum view_UpdateType type; long left; long top; long width; long height; \{ struct helloworld *hw=(struct helloworld *)hwv>header.view.dataobject; struct rectangle myVisualRect,rec; helloworldview_GetVisualBounds(hwv,&myVisualRect); hwv->vrWidth=rectangle_Width(&myVisualRect); hwv->vrHeight=rectangle_Height(&myVisualRect); if (hwv->newFrameX+hwv->vrWidth>TOTALSIZE) hwv->newFrameX=TOTALSIZE-hwv->vrWidth; if (hwv->newFrameY+hwv->vrHeight>TOTALSIZE) hwv->newFrameY=TOTALSIZE-hwv->vrHeight; hwv->frameX=hwv->newFrameX; hwv->frameY=hwv->newFrameY; if(hw->x==POSUNDEF)\{ hw->x=hwv->frameX+(hwv->vrWidth-WIDTH)/2; hw->y=hwv->frameY+(hwv->vrHeight-HEIGHT)/2; \} hwv->x=hw->x; hwv->y=hw->y; hwv->blackOnWhite=hw->blackOnWhite; /* rec is the rectangle in which the child view */ /* will be displayed, plus a one pixel border. */ \bold{rectangle_SetRectSize(&rec, hwv->x-hwv->frameX-1,hwv->y-hwv-> frameY-1,WIDTH+1,HEIGHT+1);} helloworldview_SetTransferMode(hwv,graphic_COPY); if(hw->blackOnWhite)\{ helloworldview_FillRect(hwv, &myVisualRect, helloworldview_WhitePattern(hwv)); /* if on white background, draw a rectangle around it*/ \bold{helloworldview_DrawRect(hwv,&rec);} \} else\{ helloworldview_FillRect(hwv, &myVisualRect, helloworldview_BlackPattern(hwv)); /* Most views expect a white background,*/ /* so make one for the sub-view */ helloworldview_FillRect (hwv, &rec, helloworldview_WhitePattern (hwv));] \} /* Get rid of the border pixel */ \bold{ rec.top++; rec.left++; rec.width--; rec.height--;} /* InsertView sets the child view's coordinate system */ \bold{view_InsertView(hwv->applayer,hwv,&rec);} /* FullUpdate draws the child view */ \bold{view_FullUpdate(hwv>applayer,view_FullRedraw,0,0,WIDTH,HEIGHT);} \} } Rather than negotiate about size, the statement \italic{rectangle_SetRectSize (&rec, hwv->x-hwv->frameX-1, hwv->y-hwv-> frameY-1, \smaller{WIDTH}+1, \smaller{HEIGHT}+1)} calculates a size for the \italic{hwv}'s child based on \italic{hwv}'s size. The statement \italic{view_InsertView (hwv->applayer, hwv, &rec)} sets \italic{hwv}'s child to have the logical and visible coordinates specified by \italic{&rec}. The statement \italic{view_FullUpdate(hwv>applayer,view_FullRedraw,0,0,\smaller{WIDTH,HEIGHT\ })} calls the child's \italic{FullUpdate} method. \subsection{Updating the screen partially} \indexi{Update} If you are creating a view that will have children, then you may need to handle partial updates to the children. The following \italic{Update} method from Example 17 is illustrative: \formatnote{ void helloworldview__Update(hwv) struct helloworldview *hwv; \{ struct helloworld *hw=(struct helloworld *)hwv>header.view.dataobject; helloworldview_SetTransferMode(hwv, graphic_COPY); \formatnote{if(hwv->x!=hw->x || hwv->y!=hw->y || hwv->frameX!=hwv->newFrameX || hwv->frameY!=hwv->newFrameY || hwv->blackOnWhite!=hw->blackOnWhite)\{ struct rectangle rec; if(hwv->x!=hw->x || hwv->y!=hw->y)\{ static char buf[100]; sprintf(buf,"Hello world at (%d,%d)",hw->x,hw->y); message_DisplayString(hwv,0,buf); \} } if(hw->blackOnWhite!=hwv->blackOnWhite)\{ struct rectangle vr; helloworldview_GetVisualBounds(hwv,&vr); \formatnote{ if(hw->blackOnWhite) helloworldview_FillRect(hwv,&vr,helloworldview_WhitePattern(hwv)); else helloworldview_FillRect(hwv,&vr,helloworldview_BlackPattern(hwv)); hwv->blackOnWhite=hw->blackOnWhite; } \} /* includes 1 pixel border */ \formatnote{rectangle_SetRectSize(&rec, hwv->x-hwv->frameX-1,hwv->y-hwv-> frameY-1,WIDTH+2,HEIGHT+2); /* erase the old child view */ if(hw->blackOnWhite) helloworldview_FillRect(hwv,&rec, helloworldview_WhitePattern(hwv)); else helloworldview_FillRect(hwv, &rec,helloworldview_BlackPattern(hwv)) } hwv->x=hw->x; hwv->y=hw->y; hwv->frameX=hwv->newFrameX; hwv->frameY=hwv->newFrameY; /* Re-set the child view's size and coordinates */ \formatnote{ rectangle_SetRectSize(&rec, hwv->x-hwv->frameX,hwv->y-hwv->frameY, WIDTH,HEIGHT); view_InsertView(hwv->applayer,hwv,&rec); /* Completely redraw it, since it's moved */ if(hw->blackOnWhite) helloworldview_DrawRectSize(hwv, hwv->x-hwv->frameX-1, hwv->y-hwv->frameY-1, WIDTH+1,HEIGHT+1); view_FullUpdate(hwv->applayer,view_FullRedraw,0,0,WIDTH,HEIGHT); } /* If the view hasn't moved, simply let it do any updating that it needs to */ \bold{ \}else view_Update(hwv->applayer); }\} } \subsection{Passing mouse events to children} \indexi{Mouse events} \indexi{ \italic{Hit}} In general, it is up to the parent view to pass interesting events down to its children by calling the child's specialized methods. There is also one standard view method that a parent view often invokes in a child view: the \italic{Hi}t method. Quite often when a mouse hit occurs in a region of the screen allocated to a child view, the correct thing to do is simply pass the mouse hit to the child view. Example 17's Hit method illustrates this strategy: \formatnote{ struct view *helloworldview__Hit(hwv,action,x,y,numberOfClicks) struct helloworldview *hwv; enum view_MouseAction action; long x; long y; long numberOfClicks; \{ struct helloworld *hw=(struct helloworld *)hwv>header.view.dataobject; /* We send mouse hits to the child, hwv->view */ /* if they fall within its visible rectangle.*/ if(!hwv->HaveDownTransition &&\formatnote{ x>=(hwv->x-hwv->frameX) && x<(hwv->x-hwv->frameX+WIDTH) && y>=(hwv->y-hwv->frameY) && y<(hwv->y-hwv->frameY+HEIGHT))\{ view_Hit(hwv->applayer, action, x-(hwv->x-hwv->frameX), y-(hwv->y-hwv->frameY), numberOfClicks); } if(hwv->HaveDownTransition) switch(action)\{ case view_RightUp: hwv->HaveDownTransition=FALSE; /* fall through */ case view_RightMovement: hw->x+=x-hwv->hitX; hw->y+=y-hwv->hitY; hwv->hitX=x; hwv->hitY=y; break; case view_LeftUp: hwv->HaveDownTransition=FALSE; hw->x=x+hwv->frameX; hw->y=y+hwv->frameY; break; case view_LeftMovement: /* do nothing */ break; default: /* re-synchronize mouse */ hwv->HaveDownTransition=FALSE; \} if(!hwv->HaveDownTransition) switch(action)\{ case view_RightDown: hwv->hitX=x; hwv->hitY=y; /* fall through */ case view_LeftDown: hwv->HaveDownTransition=TRUE; helloworldview_WantInputFocus(hwv,hwv); break; \} helloworld_NotifyObservers(hw,0); return (struct view *)hwv; \} } Note that two of the parameters to the Hit method are the \italic{x} and \italic{y} coordinates of the mouse hit, and these coordinates are expressed relative to the parent view's local bounds rectangle, so the parent view's Hit method must subtract the difference in origins between its view and the child's view before calling the child's Hit method. Don't forget that the child's Hit method returns a value, so you should return this value as the value of your own Hit method. \subsection{Menus and keystates} \indexi{Menus} \indexi{Keystate} \indexi{Keymap} \indexi{Input focus} The child's \italic{Hit} method may request the input focus. If granted the input focus, the child view's \italic{ReceiveInputFocus} method may post menus or keystates. The parent view has full control over these possibilities. In particular, the parent view may intercept, and possibly ignore, requests for the input focus from child views by providing a \italic{view_WantInputFocus} method. This method will be invoked every time a child (or any descendant) of the parent view requests the input focus. The first parameter to this method will be the parent, and the second parameter will be the view that desires the input focus. The parent method can discard the request by simply returning, in which case the descendant will never receive the input focus. Alternatively, the parent can permit the request to continue up the view tree by invoking its own parent's \italic{view_WantInputFocus} method with the first parameter equal to the its parent's view and the second parameter being the view desiring the input focus. Once a child view is granted the input focus by Andrew Toolkit, its \italic{ReceiveInputFocus} method will be called. In this method, a child view may post menus or keystates (providing key-to-command binds). Again, a parent view may intercept these requests and modify them. To intercept menu posting requests from its children, a parent should provide a \italic{PostMenus} method; to intercept keystate posting requests from your children, the parent should provide a \italic{PostKeyState} method. Note that if you do not provide \italic{PostMenus} or \italic{PostKeyState} methods, defaults will be provided that allow you children's posts to proceed up the view tree unimpeded. Your view's \italic{PostMenus} method is called with two parameters, the first being a pointer to your view and the second being the menu list being posted by one of your child views. Your \italic{PostMenus} method has several options. It can discard the request by simply returning, which will result in the child's menus being discarded. It can allow the request by calling its own parent's \italic{PostMenus} method with the same menu list. Finally, your view can change the menulist being posted, by either making a copy of the menulist with changes of your own, chaining your own menulist changes to the original menulist or completely replacing the menulist with a new menulist of your own design (see Menu List, Vol. II). Your view's \italic{PostKeyState} method is called with two parameters, a pointer to your view and a pointer to the keystate being posted. Your view can allow the post to continue unimpeded by invoking its own parent with the same keystate structure. Your view can discard the keystate by simply returning, in which case the last keystate actually posted will remain in effect. Or your view can post its own keystate, by invoking its parent's \italic{PostKeyState} method with a new second parameter. Keystates can be threaded together in a list, just like menulists (Keystate, Vol. II). Remaining differences are specific to Example 17 and are not of general interest. \begindata{bp,538929032} \enddata{bp,538929032} \view{bpv,538929032,953,0,0} \begindata{texttag,539315976} \textdsversion{12} ExSeventeenListing\ \enddata{texttag,539315976} \view{texttagv,539315976,954,0,0} \section{Program listing for Example 17} \formatnote{ \bold{hello.ch} #define POSUNDEF (-1) class helloworld[hello]: dataobject [dataobj]\{ overrides: Read(FILE *file,long id) returns long; Write(FILE *file,long writeId,int level) returns long; classprocedures: InitializeObject(struct helloworld *hw) returns boolean; FinalizeObject(struct helloworld *hw); data: long x,y; boolean blackOnWhite; struct dataobject *dobj; \}; \bold{hello.c} #include <stdio.h> #include <class.h> #include "hello.eh" #include "dataobj.ih" #include "text.ih" #include "style.ih" struct dataobject *createInitialDobj() \{ struct text *text=text_New(); struct style *bold=style_New(),*italic=style_New(); style_SetName(bold,"bold"); style_AddNewFontFace(bold,fontdesc_Bold); style_SetName(italic,"italic"); style_AddNewFontFace(italic,fontdesc_Italic); text_InsertCharacters(text,0,"Hello world!",sizeof("Hello world!")1); text_AddStyle(text,0,5,bold); text_AddStyle(text,6,5,italic); return (struct dataobject *)text; \} boolean helloworld__InitializeObject(classID,hw) struct classheader *classID; struct helloworld *hw; \{ hw->x = POSUNDEF; hw->y = POSUNDEF; hw->blackOnWhite = TRUE; hw->dobj=createInitialDobj(); return TRUE; \} void helloworld__FinalizeObject(classID,hw) struct classheader *classID; struct helloworld *hw; \{ dataobject_Destroy(hw->dobj); \} long helloworld__Read(hw,file,id) struct helloworld *hw; FILE *file; long id; \{ char buf[100],classNameBuf[100]; long retVal,dobjObjId; helloworld_SetID(hw,helloworld_UniqueID(hw)); if(fgets(buf,sizeof(buf),file)==NULL || /* the %hd tells scanf that blackOnWhite is a short, not an int */ sscanf(buf,"%d %d %hd\\n",&hw->x,&hw->y,&hw->blackOnWhite)<3 || fgets(buf,sizeof(buf),file)==NULL || sscanf(buf,"\\\\begindata\{%[^,],%d\}\\n", classNameBuf,&dobjObjId)<2) retVal=dataobject_PREMATUREEOF; else\{ retVal=dataobject_Read(hw->dobj,file,id); if(retVal==dataobject_NOREADERROR) if(fgets(buf,sizeof(buf),file)==NULL) /* read in the \\enddata\{...\} */ retVal=dataobject_MISSINGENDDATAMARKER; \} return retVal; \} long helloworld__Write(hw,file,writeId,level) struct helloworld *hw; FILE *file; long writeId; int level; \{ if(writeId!=helloworld_GetWriteID(hw))\{ /* only write a given version once */ helloworld_SetWriteID(hw,writeId); fprintf(file,"\\\\begindata\{%s,%d\}\\n", class_GetTypeName(hw), helloworld_UniqueID(hw)); fprintf(file,"%d %d %d\\n",hw->x,hw->y,hw->blackOnWhite); dataobject_Write(hw->dobj,file,writeId,level); fprintf(file,"\\\\enddata\{%s,%d\}\\n", class_GetTypeName(hw), helloworld_UniqueID(hw)); \} return helloworld_UniqueID(hw); \} \bold{hellov.ch} #include "rect.h" class helloworldview[hellov]: view \{ overrides: SetDataObject(struct helloworld *hw); FullUpdate(enum view_UpdateType type, long left, long top, long width, long height); Update(); Hit (enum view_MouseAction action, long x, long y, long numberOfClicks) returns struct view *; ReceiveInputFocus(); LoseInputFocus(); GetInterface(int type) returns char *; GetApplicationLayer() returns struct view *; DeleteApplicationLayer(struct view *); LinkTree(struct view *parent); classprocedures: InitializeClass() returns boolean; data: struct keystate *keystate; struct menulist *menulist; boolean HaveDownTransition; boolean haveInputFocus; long hitX,hitY; int x,y; boolean blackOnWhite; long frameX, frameY; long newFrameX, newFrameY; int vrWidth,vrHeight; struct view *view; struct view *applayer; \}; \bold{hellov.c} #include <stdio.h> #include <class.h> #include "hellov.eh" #include "graphic.ih" #include "fontdesc.ih" #include "rect.h" #include "point.h" #include "keymap.ih" #include "keystate.ih" #include "menulist.ih" #include "scroll.ih" #include "bind.ih" #include "message.ih" #include "im.ih" #include "dataobj.ih" #include "view.ih" #include "hello.ih" #define TOTALSIZE 1500 static void xgetinfo(), xsetframe(), ygetinfo(), ysetframe(); static long xwhat(), ywhat(); static struct scrollfns horizInterface = \{ xgetinfo, xsetframe, NULL, xwhat \}; static struct scrollfns vertInterface = \{ ygetinfo, ysetframe, NULL, ywhat \}; static struct keymap *helloworldviewKeymap; static struct menulist *helloworldviewMenulist; boolean helloworldview__InitializeObject(classID,hwv) struct classheader *classID; struct helloworldview *hwv; \{ hwv->haveInputFocus=FALSE; hwv->HaveDownTransition=FALSE; hwv->keystate=keystate_Create(hwv,helloworldviewKeymap); hwv->menulist=menulist_DuplicateML(helloworldviewMenulist,hwv); hwv->newFrameX=0; hwv->newFrameY=0; hwv->view=NULL; hwv->applayer=NULL; return TRUE; \} void helloworldview__FinalizeObject(classID,hwv) struct classheader *classID; struct helloworldview *hwv; \{ if(hwv->view!=NULL)\{ view_DeleteApplicationLayer(hwv->view,hwv->applayer); view_Destroy(hwv->view); \} \} void helloworldview__LinkTree(hwv,parent) struct helloworldview *hwv; struct view *parent; \{ if(hwv->applayer!=NULL) view_LinkTree(hwv->applayer,hwv); super_LinkTree(hwv,parent); \} void helloworldview__SetDataObject(hwv,hw) struct helloworldview *hwv; struct helloworld *hw; \{ hwv->x=hw->x; hwv->y=hw->y; hwv->blackOnWhite=hw->blackOnWhite; hwv->view=(struct view *)class_NewObject(dataobject_ViewName(hw>dobj)); hwv->applayer=view_GetApplicationLayer(hwv->view); view_SetDataObject(hwv->view,hw->dobj); super_SetDataObject(hwv,hw); \} struct view *helloworldview__GetApplicationLayer(hwv) struct helloworldview *hwv; \{ return (struct view *)scroll_Create(hwv,scroll_LEFT+scroll_BOTTOM); \} void helloworldview__DeleteApplicationLayer(hwv,scrollbar) struct helloworldview *hwv; struct scroll *scrollbar; \{ scroll_Destroy(scrollbar); \} #define WIDTH 100 #define HEIGHT 100 void helloworldview__FullUpdate(hwv,type,left,top,width,height) struct helloworldview *hwv; enum view_UpdateType type; long left; long top; long width; long height; \{ struct helloworld *hw=(struct helloworld *)hwv>header.view.dataobject; struct rectangle myVisualRect,rec; helloworldview_GetVisualBounds(hwv,&myVisualRect); hwv->vrWidth=rectangle_Width(&myVisualRect); hwv->vrHeight=rectangle_Height(&myVisualRect); if (hwv->newFrameX+hwv->vrWidth>TOTALSIZE) hwv->newFrameX=TOTALSIZE-hwv->vrWidth; if (hwv->newFrameY+hwv->vrHeight>TOTALSIZE) hwv->newFrameY=TOTALSIZE-hwv->vrHeight; hwv->frameX=hwv->newFrameX; hwv->frameY=hwv->newFrameY; if(hw->x==POSUNDEF)\{ hw->x=hwv->frameX+(hwv->vrWidth-WIDTH)/2; hw->y=hwv->frameY+(hwv->vrHeight-HEIGHT)/2; \} hwv->x=hw->x; hwv->y=hw->y; hwv->blackOnWhite=hw->blackOnWhite; rectangle_SetRectSize(&rec, hwv->x-hwv->frameX-1,hwv->y-hwv-> frameY-1, WIDTH+1,HEIGHT+1); helloworldview_SetTransferMode(hwv,graphic_COPY); if(hw->blackOnWhite)\{ helloworldview_FillRect(hwv,&myVisualRect, helloworldview_WhitePattern(hwv)); /* if on white background, draw a rectangle around it */ helloworldview_DrawRect(hwv,&rec); \}else\{ helloworldview_FillRect(hwv,&myVisualRect, helloworldview_BlackPattern(hwv)); helloworldview_FillRect(hwv,&rec, helloworldview_WhitePattern(hwv)); \} rec.top++; rec.left++; rec.width--; rec.height--; view_InsertView(hwv->applayer,hwv,&rec); view_FullUpdate(hwv->applayer,view_FullRedraw,0,0,WIDTH,HEIGHT); \} void helloworldview__Update(hwv) struct helloworldview *hwv; \{ struct helloworld *hw=(struct helloworld *)hwv>header.view.dataobject; helloworldview_SetTransferMode(hwv, graphic_COPY); if(hwv->x!=hw->x || hwv->y!=hw->y || hwv->frameX!=hwv->newFrameX || hwv->frameY!=hwv->newFrameY || hwv->blackOnWhite!=hw->blackOnWhite)\{ struct rectangle rec; if(hwv->x!=hw->x || hwv->y!=hw->y)\{ static char buf[100]; sprintf(buf,"Hello world at (%d,%d)",hw->x,hw->y); message_DisplayString(hwv,0,buf); \} if(hw->blackOnWhite!=hwv->blackOnWhite)\{ struct rectangle vr; helloworldview_GetVisualBounds(hwv,&vr); if(hw->blackOnWhite) helloworldview_FillRect(hwv,&vr, helloworldview_WhitePattern(hwv)); else helloworldview_FillRect(hwv,&vr, helloworldview_BlackPattern(hwv)); hwv->blackOnWhite=hw->blackOnWhite; \} /* includes 1 pixel border */ rectangle_SetRectSize(&rec, hwv->x-hwv->frameX-1,hwv->y-hwv-> frameY-1, WIDTH+2,HEIGHT+2); if(hw->blackOnWhite) helloworldview_FillRect(hwv, &rec, helloworldview_WhitePattern(hwv)); else helloworldview_FillRect(hwv, &rec, helloworldview_BlackPattern(hwv)); hwv->x=hw->x; hwv->y=hw->y; hwv->frameX=hwv->newFrameX; hwv->frameY=hwv->newFrameY; rectangle_SetRectSize(&rec,hwv->x-hwv->frameX, hwv->y-hwv->frameY,WIDTH,HEIGHT); view_InsertView(hwv->applayer,hwv,&rec); if(hw->blackOnWhite) helloworldview_DrawRectSize(hwv,hwv->x-hwv-> frameX-1, hwv->y-hwv->frameY-1,WIDTH+1,HEIGHT+1); view_FullUpdate(hwv->applayer,view_FullRedraw,0,0, WIDTH,HEIGHT); \}else view_Update(hwv->applayer); \} struct view *helloworldview__Hit(hwv,action,x,y,numberOfClicks) struct helloworldview *hwv; enum view_MouseAction action; long x; long y; long numberOfClicks; \{ struct helloworld *hw=(struct helloworld *)hwv>header.view.dataobject; if(!hwv->HaveDownTransition && x>=(hwv->x-hwv->frameX) && x<(hwv->x-hwv->frameX+WIDTH) && y>=(hwv->y-hwv->frameY) && y<(hwv->y-hwv->frameY+HEIGHT)) return view_Hit(hwv->applayer, action, x-(hwv->x-hwv->frameX), y-(hwv->y-hwv->frameY), numberOfClicks); if(hwv->HaveDownTransition) switch(action)\{ case view_RightUp: hwv->HaveDownTransition=FALSE; /* fall through */ case view_RightMovement: hw->x+=x-hwv->hitX; hw->y+=y-hwv->hitY; hwv->hitX=x; hwv->hitY=y; break; case view_LeftUp: hwv->HaveDownTransition=FALSE; hw->x=x+hwv->frameX; hw->y=y+hwv->frameY; break; case view_LeftMovement: /* do nothing */ break; default: /* re-synchronize mouse */ hwv->HaveDownTransition=FALSE; \} if(!hwv->HaveDownTransition) switch(action)\{ case view_RightDown: hwv->hitX=x; hwv->hitY=y; /* fall through */ case view_LeftDown: hwv->HaveDownTransition=TRUE; helloworldview_WantInputFocus(hwv,hwv); break; \} helloworld_NotifyObservers(hw,0); return (struct view *)hwv; \} void helloworldview__ReceiveInputFocus(hwv) struct helloworldview *hwv; \{ hwv->haveInputFocus=TRUE; hwv->keystate->next=NULL; helloworldview_PostKeyState(hwv,hwv->keystate); helloworldview_PostMenus(hwv,hwv->menulist); \} void helloworldview__LoseInputFocus(hwv) struct helloworldview *hwv; \{ hwv->haveInputFocus=FALSE; \} static void Center(hwv,rock) struct helloworldview *hwv; long rock; \{ struct helloworld *hw=(struct helloworld *)hwv>header.view.dataobject; hw->x = hwv->newFrameX + hwv->vrWidth / 2; hw->y = hwv->newFrameY + hwv->vrHeight / 2; helloworld_NotifyObservers(hw,0); \} static void Invert(hwv, rock) struct helloworldview *hwv; long rock; \{ struct helloworld *hw=(struct helloworld *)hwv>header.view.dataobject; hw->blackOnWhite=!hw->blackOnWhite; helloworld_NotifyObservers(hw,0); \} static void relocate(hwv,rock) struct helloworldview *hwv; long rock; \{ struct helloworld *hw=(struct helloworld *)hwv>header.view.dataobject; char buf[100]; int x,y; message_AskForString(hwv,0,"New location (x,y): ",NULL,buf,sizeof(buf)); if(sscanf(buf,"%d,%d",&x,&y)!=2) message_DisplayString(hwv,1,"Bad format; must be: number,number"); else\{ hw->x=x; hw->y=y; helloworld_NotifyObservers(hw,0); \} \} static void readHW(hwv,rock) struct helloworldview *hwv; long rock; \{ char file[100], msgBuf[100]; FILE *fp; message_AskForString(hwv,0,"Read file: ",NULL,file,sizeof(file)); fp=fopen(file,"r"); if(fp==NULL)\{ sprintf(msgBuf,"Couldn't open %s for reading.", file); message_DisplayString(hwv,1,msgBuf); \}else\{ char header[100]; if(fgets(header,sizeof(header),fp)==NULL)\{ sprintf(msgBuf,"Premature end-of-file in %s.",file); message_DisplayString(hwv,1,msgBuf); \}else\{ char name[20]; int id; if(sscanf(header,"\\\\begindata\{%[^,],%d\}\\n", name,&id)!=2)\{ sprintf(msgBuf, "%s doesn't contain a valid datastream header.", file); message_DisplayString(hwv,1,msgBuf); \}else\{ struct helloworld *hw= (struct helloworld *)hwv->header.view.dataobject; if(strcmp(name,class_GetTypeName(hw))!=0)\{ sprintf(msgBuf, "%s doesn't contain a helloworld dataobject.", file); message_DisplayString(hwv,1,msgBuf); \}else\{ /* FINALLY, read the object in... */ helloworld_Read(hw,fp,id); fclose(fp); helloworld_NotifyObservers(hw,0); \} \} \} \} \} static void writeHW(hwv,rock) struct helloworldview *hwv; long rock; \{ char file[100], msgBuf[100]; FILE *fp; message_AskForString(hwv,0,"Write file: ",NULL,file,sizeof(file)); fp=fopen(file,"w"); if(fp==NULL)\{ sprintf(msgBuf,"Couldn't open %s for writing.",file); message_DisplayString(hwv,1,msgBuf); \}else\{ struct helloworld *hw= (struct helloworld *)hwv->header.view.dataobject; helloworld_Write(hw,fp,im_GetWriteID(),0); fclose(fp); \} \} static void xgetinfo(hwv, total, seen, dot) struct helloworldview *hwv; struct range *total, *seen, *dot; \{ struct helloworld *hw=(struct helloworld *)hwv>header.view.dataobject; total->beg = 0; total->end = TOTALSIZE; seen->beg = hwv->frameX; seen->end = hwv->frameX + hwv->vrWidth; dot->beg = dot->end = hw->x; \} static void ygetinfo(hwv, total, seen, dot) struct helloworldview *hwv; struct range *total, *seen, *dot; \{ struct helloworld *hw=(struct helloworld *)hwv>header.view.dataobject; total->beg = 0; total->end = TOTALSIZE; seen->beg = hwv->frameY; seen->end = hwv->frameY + hwv->vrHeight; dot->beg = dot->end = hw->y; \} static void xsetframe(hwv, posn, cord, outof) struct helloworldview *hwv; int posn; long cord, outof; \{ hwv->newFrameX = posn - hwv->vrWidth * cord / outof; if (hwv->newFrameX + hwv->vrWidth > TOTALSIZE) hwv->newFrameX = TOTALSIZE - hwv->vrWidth; else if (hwv->newFrameX < 0) hwv->newFrameX = 0; helloworldview_WantUpdate(hwv, hwv); \} static void ysetframe(hwv, posn, cord, outof) struct helloworldview *hwv; int posn; long cord, outof; \{ hwv->newFrameY = posn - hwv->vrHeight * cord / outof; if (hwv->newFrameY + hwv->vrHeight > TOTALSIZE) hwv->newFrameY = TOTALSIZE - hwv->vrHeight; else if (hwv->newFrameY < 0) hwv->newFrameY = 0; helloworldview_WantUpdate(hwv, hwv); \} static long xwhat(hwv, cord, outof) struct helloworldview *hwv; long cord, outof; \{ return hwv->frameX + hwv->vrWidth * cord / outof; \} static long ywhat(hwv, cord, outof) struct helloworldview *hwv; long cord, outof; \{ return hwv->frameY + hwv->vrHeight * cord / outof; \} char *helloworldview__GetInterface(hwv, type) struct helloworldview *hwv; char *type; \{ if (strcmp(type, "scroll,vertical") == 0) return (char *) &vertInterface; else if (strcmp(type, "scroll,horizontal") == 0) return (char *) &horizInterface; else return NULL; \} static struct bind_Description helloworldviewBindings[]=\{ \{"helloworldview-center", "\\003",0, "Hello World,Center",0,0, Center, "Center the helloworldview string."\}, \{"helloworldview-invert", "\\011",0, "Hello World,Invert",0,0, Invert, "Invert the helloworldview string."\}, \{"helloworldview-relocate", "\\022",0, "Hello World,Relocate",0,0, relocate, "Relocate the helloworld string."\}, \{"helloworldview-read", NULL,0, "Hello World,Read",0,0, readHW, "Read in a new hello world."\}, \{"helloworldview-write", NULL,0, "Hello World,Write",0,0, writeHW, "Write out the current hello world to a file."\}, NULL \}; boolean helloworldview__InitializeClass(classID) struct classheader *classID; \{ helloworldviewMenulist=menulist_New(); helloworldviewKeymap=keymap_New(); bind_BindList(helloworldviewBindings, helloworldviewKeymap, helloworldviewMenulist, &helloworldview_classinfo); return TRUE; \} \bold{helloa.ch} class helloworldapp[helloa] : application[app]\{ overrides: Start() returns boolean; \}; \bold{helloa.c} #include <class.h> #include "helloa.eh" #include "dataobj.ih" #include "view.ih" #include "im.ih" #include "frame.ih" #include "lpair.ih" #include "text.ih" #include "textv.ih" #include "style.ih" #include "hello.ih" static struct view *appLayerOrDestroy(v) struct view *v; \{ if(v==NULL) return NULL; else\{ struct view *al=view_GetApplicationLayer(v); if(al==NULL)\{ view_Destroy(v); return NULL; \} return al; \} \} static boolean makeSplitWindow(dobj1,dobj2) struct dataobject *dobj1,*dobj2; \{ struct view *v1,*v2; struct view *al1,*al2,*lpAl; struct frame *frame; struct im *im; struct lpair *lp; al1=appLayerOrDestroy(v1=(struct view *)class_NewObject(dataobject_ViewName(dobj1))); if(al1==NULL) return FALSE; al2=appLayerOrDestroy(v2=(struct view *)class_NewObject(dataobject_ViewName(dobj2))); if(al2==NULL) \{ view_DeleteApplicationLayer(v1,al1); view_Destroy(v1); return FALSE; \} lpAl=appLayerOrDestroy((struct view *)(lp=lpair_New())); if(lpAl==NULL) \{ view_DeleteApplicationLayer(v2,al2); view_Destroy(v2); return FALSE; \} /* this call makes a left/right split, with the given * percentage allocated to the left view */ lpair_HSplit(lp,al1,al2,40 /* percent */,TRUE /* movable boundary */); frame=frame_New(); if(frame==NULL) \{ lpair_DeleteApplicationLayer(lp,lpAl); lpair_Destroy(lp); return FALSE; \} im=im_Create(NULL); if(im==NULL) \{ frame_Destroy(frame); return FALSE; \} view_SetDataObject(v1,dobj1); view_SetDataObject(v2,dobj2); frame_SetView(frame,lpAl); im_SetView(im,frame); view_WantInputFocus(v1,v1); return TRUE; \} boolean helloworldapp__Start(hwapp) struct helloworldapp *hwapp; \{ struct helloworld *hw; struct text *t; struct style *bold,*italic; if(!super_Start(hwapp)) return FALSE; hw=helloworld_New(); if(hw==NULL) return FALSE; t=text_New(); if(t==NULL) \{ helloworld_Destroy(hw); return FALSE; \} bold=style_New(); if(bold==NULL)\{ text_Destroy(t); return FALSE; \} style_AddNewFontFace(bold,fontdesc_Bold); italic=style_New(); if(italic==NULL) \{ style_Destroy(bold); return FALSE; \} style_AddNewFontFace(italic,fontdesc_Italic); text_InsertCharacters(t,0,"Hello world!",sizeof("Hello world!")-1); text_AddStyle(t,0,5,bold); text_AddStyle(t,6,5,italic); if(!makeSplitWindow((struct dataobject *)hw,(struct dataobject *)t) || !makeSplitWindow((struct dataobject *)hw,(struct dataobject *)t)) \{ style_Destroy(italic); return FALSE; \} return TRUE; \} \bold{Makefile} SRCDIR=/usr/andrew/ INCLUDES= -I. -I$\{SRCDIR\}include/atk -I$\{SRCDIR\}include INCLUDESRC = $\{SRCDIR\}include/atk CC=cc DEBUG = -g TOOLS = $\{SRCDIR\}bin/ CFLAGS= $\{DEBUG\} $\{INCLUDES\} CLASSFLAGS=$\{INCLUDES\} MAKEDO = $\{TOOLS\}makedo $\{DEBUG\} -b $\{TOOLS\} -d $\{SRCDIR\}lib CLASS = $\{TOOLS\}class .SUFFIXES: .ih .eh .ch .do .ch.ih: $\{CLASS\} $\{CLASSFLAGS\} $*.ch .ch.eh: $\{CLASS\} $\{CLASSFLAGS\} $*.ch .o.do: $\{MAKEDO\} $< all: helloa.do hello.do hellov.do helloa.do: helloa.o helloa.eh hello.do: hello.o hello.eh hellov.do: hellov.o hellov.eh helloa.o: helloa.c helloa.o: $\{INCLUDESRC\}/app.ih helloa.o: $\{INCLUDESRC\}/atom.ih helloa.o: $\{INCLUDESRC\}/dataobj.ih helloa.o: $\{INCLUDESRC\}/frame.ih helloa.o: $\{INCLUDESRC\}/graphic.ih helloa.o: $\{INCLUDESRC\}/im.ih helloa.o: $\{INCLUDESRC\}/lpair.ih helloa.o: $\{INCLUDESRC\}/message.ih helloa.o: $\{INCLUDESRC\}/namespc.ih helloa.o: $\{INCLUDESRC\}/observe.ih helloa.o: $\{INCLUDESRC\}/point.h helloa.o: $\{INCLUDESRC\}/rect.h helloa.o: $\{INCLUDESRC\}/view.ih helloa.o: $\{SRCDIR\}include/class.h helloa.o: hello.ih helloa.o: hellov.ih helloa.o: helloa.eh helloa.eh helloa.ih: helloa.ch helloa.eh helloa.ih: $\{INCLUDESRC\}/app.ih hello.o: hello.c hello.o: $\{INCLUDESRC\}/atom.ih hello.o: $\{INCLUDESRC\}/dataobj.ih hello.o: $\{INCLUDESRC\}/namespc.ih hello.o: $\{INCLUDESRC\}/observe.ih hello.o: $\{SRCDIR\}include/class.h hello.o: hello.eh hello.eh hello.ih: hello.ch hello.eh hello.ih: $\{INCLUDESRC\}/atom.ih hello.eh hello.ih: $\{INCLUDESRC\}/dataobj.ih hello.eh hello.ih: $\{INCLUDESRC\}/namespc.ih hello.eh hello.ih: $\{INCLUDESRC\}/observe.ih hellov.o: hellov.c hellov.o: $\{INCLUDESRC\}/atom.ih hellov.o: $\{INCLUDESRC\}/bind.ih hellov.o: $\{INCLUDESRC\}/dataobj.ih hellov.o: $\{INCLUDESRC\}/graphic.ih hellov.o: $\{INCLUDESRC\}/keymap.ih hellov.o: $\{INCLUDESRC\}/keystate.ih hellov.o: $\{INCLUDESRC\}/menulist.ih hellov.o: $\{INCLUDESRC\}/message.ih hellov.o: $\{INCLUDESRC\}/namespc.ih hellov.o: $\{INCLUDESRC\}/observe.ih hellov.o: $\{INCLUDESRC\}/point.h hellov.o: $\{INCLUDESRC\}/rect.h hellov.o: $\{INCLUDESRC\}/scroll.ih hellov.o: $\{INCLUDESRC\}/view.ih hellov.o: $\{SRCDIR\}include/class.h hellov.o: hello.ih hellov.o: hellov.eh hellov.eh hellov.ih: hellov.ch hellov.eh hellov.ih: $\{INCLUDESRC\}/graphic.ih hellov.eh hellov.ih: $\{INCLUDESRC\}/observe.ih hellov.eh hellov.ih: $\{INCLUDESRC\}/point.h hellov.eh hellov.ih: $\{INCLUDESRC\}/rect.h hellov.eh hellov.ih: $\{INCLUDESRC\}/view.ih hellov.eh hellov.ih: $\{SRCDIR\}include/class.h } \begindata{bp,537558784} \enddata{bp,537558784} \view{bpv,537558784,956,0,0} Copyright 1992 Carnegie Mellon University and IBM. All rights reserved. \smaller{\smaller{$Disclaimer: # Permission to use, copy, modify, and distribute this software and its # documentation for any purpose and without fee is hereby granted, provided # that the above copyright notice appear in all copies and that both that # copyright notice and this permission notice appear in supporting # documentation, and that the name of IBM not be used in advertising or # publicity pertaining to distribution of the software without specific, # written prior permission. # # THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ANY COPYRIGHT # HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL # DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, # DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # $ }}\enddata{text,538489940}