#ifdef TALKTOREXX /* * This program is an example of how to add a Rexx port to a given * program. I thought it particularly appropriate to grab a program * off Fred Fish Disk 1. All the REXX stuff is bracketed by `ifdef * TALKTOREXX', so you can identify it easily. If you compile with * TALKTOREXX unset, you will get the default program with no REXX * port. * * The REXX port on this program adds another 3K to the executable * size, but a lot of functionality comes with that. You can draw * from Rexx, spawn macros from Rexx, etc. But go to the next * TALKTOREXX for more information. * * To run a rexx macro on startup, simply give that rexx macro as * part of the command line, as in * * freedraw sample * * or * * freedraw bspline 20 100 20 20 280 20 280 100 * * All modifications are by Radical Eye Software, and all modifications * are placed in the public domain. */ #endif /************************************************************************/ /*** FreeDraw - PD graphics for Amiga ***/ /*** ***/ /*** This is an extremely simple graphics editor which works in ***/ /*** the windowing environment of the Amiga. It is very limited ***/ /*** in features, but I hope to add a lot more, and I would be ***/ /*** happy to receive assistance from anyone who wants to give it. ***/ /*** The basic idea of this program is to provide some minimal ***/ /*** image editing functions which can be used to develop images ***/ /*** for other programs. I know there will be a lot of Paint type ***/ /*** type programs avaialable soon, but what are we supposed to use ***/ /*** now? The most important features to add now will probably be ***/ /*** those related to "cut and paste", disk srtorage and retrieval, ***/ /*** and "single-pixel" editing like Mac's "fatbits". ***/ /*** I intend to use the IFF standard for the image storage and ***/ /*** retrieval and will be coding a "Files" menu soon. The work ***/ /*** required for "cut and paste" should be almost trivial, but I ***/ /*** still may not get to it for a while. Fatbits editing from one ***/ /*** window to another involves some manipulations of the RastPorts ***/ /*** which still elude me, as I have only recently begun to use the ***/ /*** Amiga and don't yet understand some important details of these ***/ /*** structures. This would be a great item for one of the genius ***/ /*** members of the Amiga programming community to provide. ***/ /*** There are only two menu topics in this version, so using it ***/ /*** is really quite easy. Boxes are not allowed to be drawn in ***/ /*** areas outside of the window where border gadgets are located, ***/ /*** and the pen-draw mode also clips to these same boundaries. If ***/ /*** you have begun to draw a box by clicking the left button while ***/ /*** the cursor is located in the FreeDraw window, then you can ***/ /*** cancel that box by clicking the right button. In the pen mode ***/ /*** pressing and holding the left button will draw. Colors are ***/ /*** selected by simply releasing the menu button over the desired ***/ /*** color in the Color menu. The erase feature always clears the ***/ /*** window to the currently selected color. ***/ /*** This is no gem of programming style, but you're getting it ***/ /*** for the right price so be patient with its design flaws. New ***/ /*** versions will appear here on BIX as soon as I can get them in ***/ /*** shape for release. I apologize to anyone who objects to my ***/ /*** lack of coding grace, but I just want to get the project off ***/ /*** the ground, and improvements will be forthcoming. There are ***/ /*** a lot of comments, but I didn't know what needed to be made ***/ /*** clear so I just commented everything. ***/ /*** ***/ /*** If you like the idea of a PD graphics program and would be ***/ /*** interested in doing some development work, then please write ***/ /*** me at the address listed below, or call if you prefer. I do ***/ /*** want to know if there is any interest in such a project, so ***/ /*** I will be glad to discuss any ideas you might have. Also, as ***/ /*** I do not currently use CompuServe or any other major nets, I ***/ /*** would appreciate if someone would post this listing there. ***/ /*** I hope somebody enjoys this. Have Fun. ***/ /*** Rick Ross 11/14/85 ***/ /*** ***/ /*** My address: ***/ /*** Richard M. Ross, Jr. ***/ /*** Eidetic Imaging ***/ /*** 740 N. 22nd Street ***/ /*** Philadelphia, PA 19130 ***/ /*** ***/ /*** Phone - (215) 236-7388 ***/ /************************************************************************/ char *VERSION = "Freedraw 0.01 by Richard M. Ross" ; /* compiler directives to fetch the necessary header files */ #include #include #include #include #include #include #include #include #include #include #include /* These definitions are used by intuition for * calls to OpenLibrary() in order to ensure * that an appropriate ROM revision is * available. */ #define INTUITION_REV 1L #define GRAPHICS_REV 1L /* Intuition always wants to see these declarations */ struct IntuitionBase *IntuitionBase; struct GfxBase *GfxBase; /* This is the Window structure declaration. * Nothing fancy is going on here, but note * the Flags and IDCMPFlags members of the * structure define which messages will be * sent by Intuition. I haven't used them all * and if you want to change the settings you * should probably do it her instead of using * ModifyIDCMP later. */ struct NewWindow NewWindow = { 10, 10, 600, 180, 0, 1, CLOSEWINDOW | MOUSEMOVE | MOUSEBUTTONS | MENUPICK | NEWSIZE | INACTIVEWINDOW, WINDOWCLOSE | SMART_REFRESH | ACTIVATE | WINDOWDRAG | WINDOWDEPTH | WINDOWSIZING | REPORTMOUSE, NULL, NULL, (UBYTE *)"AMIGA FreeDraw 0.01", NULL, NULL, 100, 35, -1, -1, WBENCHSCREEN, }; #ifdef TALKTOREXX /* * We need our include file. */ #include "minrexx.h" /* * These are the REXX functions defined at the bottom of the file. */ void rexxcolor(), rexxbox(), rexxfbox(), rexxline(), rexxtofront(), rexxtoback(), rexxexit(), rexxversion(), rexxspawn() ; int disp() ; /* * Here is our command association list. Note that in this case, * we are setting the userdata field to be a function to call. * Dispatch will still take place through disp(), so common head * and tail stuff can go there. * * Commands are all lower case, so we match either upper or lower. * (This is a requirement of minrexx.) */ struct rexxCommandList rcl[] = { { "color", (APTR)&rexxcolor }, { "box", (APTR)&rexxbox }, { "fbox", (APTR)&rexxfbox }, { "line", (APTR)&rexxline }, { "tofront", (APTR)&rexxtofront }, { "toback", (APTR)&rexxtoback }, { "exit", (APTR)&rexxexit }, { "version", (APTR)&rexxversion }, { "spawn", (APTR)&rexxspawn }, { NULL, NULL } } ; #endif /*******************************************************************/ /* DrawBox - Simple routine to draw an unfilled rectangle */ /* It accepts the coordinates of the top-left and lower-right */ /* points of the rectangle, a pointer to the Window structure, */ /* and the color in which to render the rectangle. The current */ /* FgPen color of the window is preserved thru the call. No */ /* clipping is done. */ /*******************************************************************/ void DrawBox( tlx, tly, brx, bry, window, color ) SHORT tlx, tly; /* top-left x,y coordinates */ SHORT brx, bry; /* lower-right x,y coordinates */ struct Window *window; /* pointer to target window */ BYTE color; /* color to use for render */ { BYTE OldColor = window->RPort->FgPen; /* save window's FgPen */ SetAPen( window->RPort, (long)color ); /* set draw color for box */ Move(window->RPort, (long)tlx, (long)tly); /* move to top-left point */ Draw(window->RPort, (long)brx, (long)tly); /* and draw to each of the */ Draw(window->RPort, (long)brx, (long)bry); /* four corners of the box */ Draw(window->RPort, (long)tlx, (long)bry); Draw(window->RPort, (long)tlx, (long)tly); SetAPen( window->RPort, (long)OldColor ); /* restore old FgPen */ } /*********************************************************/ /* Color Select Menu */ /* */ /* This is where the menu for color selection is */ /* defined. It should be flexible enough to allow for */ /* increased palette sizes, but this version is only */ /* for the 4-color mode of the WorkBench screen. */ /*********************************************************/ /* A few definitions are needed here. * Note that MAXPAL should be increased * to allow for palette larger than * four colors. */ #define ITEMSTUFF (ITEMENABLED | HIGHBOX) #define CW 40 #define CH 25 #define MAXPAL 4 /* declare enough storage for required * number of menu items and associated * images. This menu will be using * graphics renditions of menu items, * so the Image structures must be * declared. This menu is modeled after * the one found in the IconEd source. */ struct MenuItem coloritem[MAXPAL]; struct Image colorimage[MAXPAL]; /* array of palette sizes to correspond with * depth of window in bit-planes */ SHORT palette[] = { 2, 4, 8, 16, 32 }; /*****************************************************************/ /* The following function initializes the structure arrays */ /* needed to provide the Color menu topic. */ /*****************************************************************/ InitColorItems( depth ) SHORT depth; /* number of bit-planes in window */ { SHORT n, colors; colors = palette[depth-1]; for( n=0; nBorderLeft; MinY = Window->BorderTop; MaxX = Window->Width - Window->BorderRight - 1; MaxY = Window->Height - Window->BorderBottom - 1; InitColorItems( 2 ); /* initialize Color menu arrays */ InitDModeItems(); /* initialize DrawMode menu arrays */ InitMenu(); /* initialize the menu structures */ /* Now, having initialized the various arrays * of structures required for menu generation * we can tell Intuition to make our menus * available to the user when this window * is active. */ SetMenuStrip( Window, &menu[0] ); /* set initial drw mode and color */ SetDrMd( Window->RPort, JAM1 ); SetAPen( Window->RPort, DrawColor ); #ifdef TALKTOREXX /* * For rexx, we open up a Rexx port, and send out the first command, * if there was one. We send it out asynchronously; no reason not to. */ rexxbit = upRexxPort("freedraw", rcl, "fd", &disp) ; firstcommand[0] = 0 ; for (x=1; xUserPort->mp_SigBit) | rexxbit); dispRexxPort() ; #else Wait( 1L << Window->UserPort->mp_SigBit); #endif MouseMoved = FALSE; /* clear this flag each time thru loop */ /* since more than one message may be waiting * a reply at this point, a loop is used to * process all that have come in until no more * are ready. Msg received is assigned to * NewMessage from the GetMsg() function. This * value will be NULL if no message is ready, * and control passes out of the loop at that time */ while( NewMessage=(struct IntuiMessage *)GetMsg(Window->UserPort) ) { /* copy some values from the message structure * to variables used in the switch statements * below */ class = NewMessage->Class; code = NewMessage->Code; x = Window->MouseX; y = Window->MouseY; /* SIZEVERIFY is a very high priority message * in our loop and requires some immediate * servicing. Any outstanding draw operations * are immediately cancelled, and the DrawMode * is nulled. This prevents any attempts to * render outside whatever new Window boundaries * the user chooses. * * (not anymore, it don't. -tgr) if( class == SIZEVERIFY ) { PenDown = FALSE; if( RubberBox ) { DrawBox( TLX, TLY, OldBRX, OldBRY, Window, DrawColor ); Window->Flags &= ( 0xFFFFFFFF ^ RMBTRAP ); RubberBox = FALSE; } } */ /* we have all the information needed from * the message, so we can now safely reply * to it without losing data */ ReplyMsg( NewMessage ); /* Examine point coords from message received * and set the clipping flag if out of bounds. * If user was drawing in PenMode when message * was received, then the ClippedLast flag * should also be set to indicate this to the * next draw operation. */ if(ClipIt = ( x < MinX || x > MaxX || y < MinY || y > MaxY )) if( PenDown ) ClippedLast = TRUE; /* enter switch on type of message received */ switch( class ) { case MOUSEMOVE: /* Don't really do anything with this one * until any other, more important, messages * are received and processed. */ MouseMoved = TRUE; break; case NEWSIZE: /* set new clipping boundaries */ MinX = Window->BorderLeft; MinY = Window->BorderTop; MaxX = Window->Width - Window->BorderRight - 1; MaxY = Window->Height - Window->BorderBottom - 1; break; case CLOSEWINDOW: /* User is ready to quit, so indicate * that execution should terminate * with next iteration of the loop. */ KeepGoing = FALSE; break; case MOUSEBUTTONS: /* A number of things could have happened * here, and further examination of data * received from message is needed to * determine what action should be taken. * The code variable holds important info * about what actually caused the message * to be sent in the first place. */ switch ( code ) { case SELECTUP: /* User was holding down the left button * and just released it. The PenMode * flag variables are set accordingly. * The pen can no longer be down, and * ClippedLast is reset for next time. */ PenDown = ClippedLast = FALSE; break; case SELECTDOWN: /* User has pressed the left button, and * several differnt actions may need to * be taken. If the ClipIt value is TRUE, * then no action should be taken at all. */ if( ClipIt ) break; /* If user is currently in PenMode, then * set up to draw when MOUSEMOVED messages * are received until a subsequent SELECTUP * message comes in. */ if( PenMode ) { PenDown = TRUE; ClippedLast = FALSE; /* make sure to set appropriate mode */ SetDrMd( Window->RPort, JAM1 ); /* and establish initial position to draw */ Move( Window->RPort, (long)x, (long)y ); break; } /* If user is currently rubberbanding a box, * then a SELECTDOWN message means it is time * to stop rubberbanding and actually draw it. * The following code will be executed if * this is the case, and it will determine if * a filled box is needed before rendering. */ if( RubberBox ) { /* set draw mode back to JAM1 since * it is now currently set to COMPLEMENT */ SetDrMd( Window->RPort, JAM1 ); RubberBox = FALSE; /* turn off rubberbanding */ /* Restore the condition of the RMBTRAP * bit in the Window structure's Flags * member. Menubutton events will no * be received by this loop. */ Window->Flags &= ( 0xFFFFFFFF ^ RMBTRAP ); /* RectFill is not condusive to the smooth * execution of programs iit arguments are * out of order, sot his code sorts them * in preparation for the call. */ if( FilledBox ) { /* first sort the x-coords */ if( TLX < OldBRX ) { x1 = TLX; x2 = OldBRX; } else { x1 = OldBRX; x2 = TLX; } /* then sort the y-coords */ if( TLY < OldBRY ) { y1 = TLY; y2 = OldBRY; } else { y1 = OldBRY; y2 = TLY; } /* now generate the filled rectangle */ RectFill( Window->RPort, (long)x1, (long)y1, (long)x2, (long)y2 ); } else { /* FilledBox not set, so draw hollow box */ DrawBox( TLX, TLY, OldBRX, OldBRY, Window, DrawColor ); } break; } /* If execution comes here, then PenMode was * not set and user was not rubberbanding. * SELECTDOWN therefore indicates to start the * rubberbanding process at this point. The * initial coords are set to the values we * received when the GetMsg() was executed. */ TLX = OldBRX = x; TLY = OldBRY = y; /* set to render in XOR mode */ SetDrMd( Window->RPort, COMPLEMENT ); /* set flag to indicate we are now rubberbanding */ RubberBox = TRUE; /* This instruction indicates to Intuition * that we now wish to receive a message * each time the Menubutton is pressed. * This is how we hijack the right button * for temporary use as a Cancel button * instead of a Menubutton. */ Window->Flags |= RMBTRAP; /* render the initial rubberbox and exit */ DrawBox( TLX, TLY, OldBRX, OldBRY, Window, DrawColor ); break; case MENUDOWN: /* WE only receive this message class if * the RMBTRAP flag bit has been set, so * it always means that we should cancel * the box which is currently rubberbanding. */ /* turn the flag off */ RubberBox = FALSE; /* restore control of menubutton to Intuition */ Window->Flags &= ( 0xFFFFFFFF ^ RMBTRAP ); /* erase (by double XOR'ing) the current * rubberbox and exit switch. */ DrawBox( TLX, TLY, OldBRX, OldBRY, Window, DrawColor ); break; default: /* Something unimportant happened, so just * continue thru the GetMsg() loop. */ continue; } break; case MENUPICK: /* A menu event has taken place and is * ready to be processed. Examine the * code variable received from the message * to determine what action should be taken. * The first check is for MENUNULL, which * means that nothing should be done at all. */ if( code != MENUNULL ) { /* get menu and item numbers from code */ MenuNum = MENUNUM( code ); ItemNum = ITEMNUM( code ); /* determine appropriate action by menu number */ switch ( MenuNum ) { case 0: /* Menu 0 is the Color menu. The * item number indicates which new * color to set. */ DrawColor = ItemNum; SetAPen( Window->RPort, (long)DrawColor ); break; case 1: /* Menu 1 is the DrawMode menu. The item * number indicates what to do. * NOTE: Since we cannot have received * this message if we were rubberbanding, * then there is no need to clean up before * changing drawing modes. */ switch ( ItemNum ) { case 0: /* Erase window to current color */ SetDrMd( Window->RPort, JAM1 ); RectFill( Window->RPort, (long)MinX, (long)MinY, (long)MaxX, (long)MaxY); break; case 1: /* set flag variables for hollow box */ PenMode = FALSE; FilledBox = FALSE; break; case 2: /* set flag variables for filled box */ PenMode = FALSE; FilledBox = TRUE; break; case 3: /* set flag variables for PenMode */ PenMode = TRUE; break; default: /* don't do anything */ break; } break; default: /* Menu number unrecognized, do nothing */ break; } } break; case INACTIVEWINDOW: /* User has de-selected our window, so a * little bit of cleaning up may be needed * to prevent untoward events when he comes * back to it. */ /* erase any outstanding rubberbox */ if( RubberBox ) DrawBox( TLX, TLY, OldBRX, OldBRY, Window, DrawColor ); /* reset all the flafg variables */ PenDown = ClippedLast = RubberBox = FALSE; /* return possibly diverted menubutton events to Big I */ Window->Flags &= ( 0xFFFFFFFF ^ RMBTRAP ); break; default: /* message class was unrecognized, so do nothing */ break; } } /* this brace ends the while(NewMessage) loop way back when */ /* There are no more messages waiting at the * IDCMP port, so we can now proceed to * process any MOUSEMOVED message we may * have received. */ if( MouseMoved && !ClipIt) { /* the mouse did move, and we don't need to clip */ /* check first if we are drawing in PenMode */ if( PenDown ) { /* We have to examine if we clipped the * last PenMode draw operation. If we did, * then this is the first move back into * window boundaries, so we mov instead of * drawing. */ if( ClippedLast ) { ClippedLast = FALSE; /* reset this flag now */ Move( Window->RPort, (long)x, (long)y ); } else Draw( Window->RPort, (long)x, (long)y ); /* draw to x,y coords */ } else { /* We weren't in PenMode, but we still might * be rubberbanding a box. If so, then we * should erase the current rubberbox and * draw a new one with the new mouse coords. */ if( RubberBox ) { /* erase the old rubberbox - draw mode is COMPLEMENT */ DrawBox( TLX, TLY, OldBRX, OldBRY, Window, DrawColor ); /* assign new values to box coords */ OldBRX = x; OldBRY = y; /* and draw the new rubberbox */ DrawBox( TLX, TLY, OldBRX, OldBRY, Window, DrawColor ); } } } } /* It must be time to quit, so we have to clean * up and exit. */ #ifdef TALKTOREXX /* * With Rexx, we need to bring the port down. You might make this * part of exit() for programs that have multiple paths to exit. */ dnRexxPort() ; #endif ClearMenuStrip( Window ); CloseWindow( Window ); exit(TRUE); } #ifdef TALKTOREXX /* * Now we get into the actual code necessary for our REXX port; functions * that do the real work. Note that this program was not structured * particularly nicely for Rexx; I had to write each of these functions. * Many programs have these subroutines already in place; they are called * as part of the event loop. This progam, however, just has one big * switch statement with different actions . . . * * First, our locals. */ int currrexxcolor = 1 ; /* what color is *rexx* drawing in? */ int args[4] ; /* what args did we see to this function? */ int parsed ; /* was argument parsing successful? */ int userreplied ; /* has the current message been replied to yet? */ /* * This function takes a pointer to a pointer to a string, grabs the * next number, returns it, and advances the pointer to the string to * point after the number. */ int getnm(where) char **where ; { register char *p = *where ; register int val = 0 ; int gotone = 0 ; while (*p <= ' ' && *p) p++ ; while ('0' <= *p && *p <= '9') { gotone = 1 ; val = 10 * val + *p++ - '0' ; } if (gotone == 0) parsed = 0 ; *where = p ; return(val) ; } /* * This function trys to find `n' numeric arguments in the command * string, and stuffs them into the args array. */ void parseargs(p, n) char *p ; int n ; { register int i ; while (*p > ' ' && *p) p++ ; for (i=0; iRPort->FgPen ; SetAPen(Window->RPort, (long)currrexxcolor) ; ((int (*)())(dat->userdata))(msg, p) ; SetAPen(Window->RPort, (long)t) ; if (! parsed) replyRexxCmd(msg, (long)parsed, 0L, NULL) ; return ; } replyRexxCmd(msg, 20L, 10L, NULL) ; } /* * This handler sets the current rexx color. */ void rexxcolor(msg, p) struct RexxMsg *msg ; char *p ; { parseargs(p, 1) ; currrexxcolor = args[0] ; } /* * This function silently clips the x and y values at `n' to the * window bounds. */ void clipxy(n) int n ; { if (args[n] < MinX) args[n] = MinX ; if (args[n] > MaxX) args[n] = MaxX ; n++ ; if (args[n] < MinY) args[n] = MinY ; if (args[n] > MaxY) args[n] = MaxY ; } /* * This handler grabs four arguments and draws a box. */ void rexxbox(msg, p) struct RexxMsg *msg ; char *p ; { parseargs(p, 4) ; clipxy(0) ; clipxy(2) ; DrawBox(args[0], args[1], args[2], args[3], Window, currrexxcolor) ; } /* * This handler grabs four arguments and draws a filled box. */ void rexxfbox(msg, p) struct RexxMsg *msg ; char *p ; { register int t ; parseargs(p, 4) ; clipxy(0) ; clipxy(2) ; if (args[0] > args[2]) { t = args[0] ; args[0] = args[2] ; args[2] = t ; } if (args[1] > args[3]) { t = args[1] ; args[1] = args[3] ; args[3] = t ; } RectFill( Window->RPort, (long)args[0], (long)args[1], (long)args[2], (long)args[3]) ; } /* * This handler grabs four arguments and draws a line. */ void rexxline(msg, p) struct RexxMsg *msg ; char *p ; { parseargs(p, 4) ; clipxy(0) ; clipxy(2) ; Move(Window->RPort, (long)args[0], (long)args[1]) ; Draw(Window->RPort, (long)args[2], (long)args[3]) ; } /* * This handler pops the window to front. */ void rexxtofront(msg, p) struct RexxMsg *msg ; char *p ; { WindowToFront(Window) ; } /* * This handler pops the window to back. */ void rexxtoback(msg, p) struct RexxMsg *msg ; char *p ; { WindowToBack(Window) ; } /* * This handler sets the exit flag. */ void rexxexit(msg, p) struct RexxMsg *msg ; char *p ; { KeepGoing = 0 ; } /* * This handler returns the version of the program. */ void rexxversion(msg, p) struct RexxMsg *msg ; char *p ; { userreplied = 1 ; replyRexxCmd(msg, 0L, 0L, VERSION) ; } /* * This handler sends the rest of the command asynchronously, * allowing us to run macros in parallel. */ void rexxspawn(msg, p) struct RexxMsg *msg ; char *p ; { while (*p <= ' ' && *p) p++ ; asyncRexxCmd(p) ; } #endif