/*-------------------------------------------------------------*/
/*  File: ol_draw.c
 *
 *  A simple drawing program with an OPEN LOOK user interface.
 *
 */
 /*-------------------------------------------------------------*/
#include <stdio.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>


#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <Xol/OpenLook.h>

#include <Xol/FooterPane.h>
#include <Xol/ControlAre.h>
#include <Xol/ScrolledWi.h>
#include <Xol/MenuButton.h>
#include <Xol/Stub.h>
#include <Xol/Form.h>
#include <Xol/StaticText.h>


#define MAXARGS     20
#define MAXFIGURES  100

#define WIDTH       300
#define HEIGHT      200

typedef struct FIGURE
{
    short   type;      /* What type of figure          */
#define LINE          0
#define RECT          1
#define ELLIPSE       2
#define FILLRECT      3
#define FILLELLIPSE   4
    short   x1, y1;   /* Corners of bounding rectangle */
    short   x2, y2;   /* or end-points of line         */
} FIGURE;

/* Array of figures */

FIGURE  figures[MAXFIGURES];
int     numfigures = 0;
int     curfig = 0,
        figtype = 0;

static int zero=0, one=1, two=2, three=3, four=4;

GC   theGC;     /* GC for regular drawing */
GC   xorGC;     /* GC used for rubber-band drawing */

Cursor xhair_cursor;

/* Callback functions */

void start_rubberband();
void continue_rubberband();
void end_rubberband();

void figure_select();
void handle_expose();
void quit_action();

/* This function draws the figures */
static void draw_figure();

/* This function, defined in file "ol_util.c" prepares menus */
Widget MakeMenuButton();

/* These are for displaying a message in the footer area */
Widget message_area;
static char* messages[] =
{
    "Shape selected: Line",
    "Shape selected: Rectangle",
    "Shape selected: Ellipse",
    "Shape selected: Filled Rectangle",
    "Shape selected: Filled Ellipse"
};
Widget xdr;
/*-------------------------------------------------------------*/
/*  m a i n
 *
 *  Main function of the ol_draw program.
 */
void main(argc, argv)
int  argc;
char **argv;
{
    Widget    top_level, footer_panel, form, menu_bar, 
              scroll_win, drawing_area, new_menu;

    Pixel     fg, bg;
    XGCValues xgcv;

/* Create the top-level shell widget and initialize the toolkit*/       
    top_level = OlInitialize(argv[0], "Ol_draw", NULL,
                               0, &argc, argv);


/* Create the widget hierarchy for the application's base 
 * window. Use a "Stub" widget as the drawing area.
 * The widget instance hierarchy is as follows:
 *
 *         top_level
 *            |
 *        FooterPanel
 *            |
 *        ---------
 *        |        |
 *      Form     StaticText (for messages)
 *        |
 *   ----------------------------
 *   |                          |
 * ControlArea(menu_bar)   ScrolledWindow (underneath menu_bar)
 *       |                      |
 *   --------------           Stub (drawing_area)
 *   |  |  |  |...|
 *   MenuButtons
 *
 */
    footer_panel = XtVaCreateManagedWidget("footer",
                            footerPanelWidgetClass, top_level,
                            NULL);

    form = XtVaCreateManagedWidget("form",
                            formWidgetClass, footer_panel,
                            NULL);

/* Footer area for status and error messages */

    message_area = XtVaCreateManagedWidget("messages",
                            staticTextWidgetClass, footer_panel,
                            XtNstring,      messages[0],
                            XtNgravity,     WestGravity,
                            NULL);

    menu_bar = XtVaCreateManagedWidget("menubar",
                            controlAreaWidgetClass, form,
                            NULL);

/* Add the menu buttons to the menu bar */

/* Create the "File" menu -------------------------------------*/ 
    new_menu = MakeMenuButton("File", menu_bar, OL_NONE,
                            "Quit", quit_action, NULL,
                             NULL);

/* Create the "Shapes" menu -----------------------------------*/
    new_menu = MakeMenuButton("Shapes", menu_bar, OL_OUT,
              "Line",             figure_select, (caddr_t)&zero,
              "Rectangle",        figure_select, (caddr_t)&one,
              "Ellipse",          figure_select, (caddr_t)&two,
              "Filled Rectangle", figure_select, (caddr_t)&three,
              "Filled Ellipse",   figure_select, (caddr_t)&four,
              NULL);
    
/* Add a scrolled window and the drawing area within it */

    scroll_win =  XtVaCreateManagedWidget("scrollwin",
                            scrolledWindowWidgetClass, form,
                            XtNyRefWidget,    menu_bar,
                            XtNyAddHeight,    True,
                            XtNxResizable,    True,
                            XtNyResizable,    True,
                            XtNyAttachBottom, True,
                            XtNxAttachRight,  True,
                            NULL);

    drawing_area = XtVaCreateManagedWidget("stub",
                            stubWidgetClass, scroll_win,
                            XtNheight,       WIDTH,
                            XtNwidth,        HEIGHT,
                            XtNexpose,       handle_expose,
                            NULL);
xdr = drawing_area;
/* Create the GCs. First retrieve the background color
 * from the Stub widget's resources. 
 */
    XtVaGetValues(drawing_area, XtNbackground, &bg,
                  NULL);

/* Assume a black foreground color */
    fg = BlackPixelOfScreen(XtScreen(drawing_area));

/* If the foreground and background colors are the same,
 * reset foreground to "black" and background to "white"
 */
    if(fg == bg)
    {
        bg = WhitePixelOfScreen(XtScreen(drawing_area));
        XtVaSetValues(drawing_area, XtNbackground, &bg,
                      NULL);
    }
    
/* Define a GC with these colors */
    xgcv.foreground = fg;
    xgcv.background = bg;
    theGC = XtGetGC(drawing_area, GCForeground | GCBackground, 
                    &xgcv);

/* Set up a GC with exclusive-OR mode (for rubber-band drawing)*/
    xgcv.foreground = fg ^ bg;
    xgcv.background = bg;
    xgcv.function = GXxor;
    xorGC = XtGetGC(drawing_area, GCForeground |
                      GCBackground | GCFunction, &xgcv);


/* Create a cross-hair cursor for the drawing area */
    xhair_cursor = XCreateFontCursor(XtDisplay(drawing_area), 
                                     XC_crosshair); 

/* Add event handlers for button events to handle the drawing */
    XtAddEventHandler(drawing_area, ButtonPressMask, False,
                      start_rubberband, NULL);
    XtAddEventHandler(drawing_area, ButtonMotionMask, False,
                      continue_rubberband, NULL);
    XtAddEventHandler(drawing_area, ButtonReleaseMask, False,
                      end_rubberband, NULL);

/* Realize all widgets */
    XtRealizeWidget(top_level);

/* Set up everything so that the cursor changes to a cross-hair
 * and is confined to the drawing_area while the mouse button is
 * pressed. This is done through what is known as a "grab"
 */
    XGrabButton(XtDisplay(drawing_area), AnyButton, AnyModifier,
                XtWindow(drawing_area), True, ButtonPressMask|
                ButtonMotionMask | ButtonReleaseMask, 
                GrabModeAsync, GrabModeAsync,
                XtWindow(drawing_area), xhair_cursor);
                
/* Start the main event-handling loop */
    XtMainLoop();
}
/*-------------------------------------------------------------*/
/*  q u i t _ a c t i o n
 *
 *  This routine is called when the "Quit" item is selected from
 *  the "File" menu.
 */
void quit_action(w, client_data, call_data)
Widget    w;
XtPointer client_data, call_data;
{
    XtCloseDisplay(XtDisplay(w));
    exit(0);
}
/*-------------------------------------------------------------*/
/*  s t a r t _ r u b b e r b a n d
 *
 *  Start of rubber-band figure
 */
void start_rubberband(w, data, p_event)
Widget    w;
XtPointer data;
XEvent    *p_event;
{
    int x = p_event->xbutton.x,
        y = p_event->xbutton.y;

/* Crude check to ensure that we don't exceed array's capacity */
    if(numfigures > MAXFIGURES-1)
       numfigures = MAXFIGURES-1;
    curfig = numfigures;
    numfigures++;

    figures[curfig].type = figtype;
    figures[curfig].x1 = x;
    figures[curfig].y1 = y;
    figures[curfig].x2 = x;
    figures[curfig].y2 = y;
    draw_figure(XtDisplay(w), XtWindow(w), xorGC, curfig);
}
/*-------------------------------------------------------------*/
/*  c o n t i n u e _ r u b b e r b a n d
 *
 *  Handle mouse movement while drawing a rubber-band figure
 */
void continue_rubberband(w, data, p_event)
Widget    w;
XtPointer data;
XEvent    *p_event;
{
    int x = p_event->xbutton.x,
        y = p_event->xbutton.y;

/* Draw once at old location (to erase figure) */
    draw_figure(XtDisplay(w), XtWindow(w), xorGC, curfig);

/* Now update end-point and redraw */
    figures[curfig].x2 = x;
    figures[curfig].y2 = y;
    draw_figure(XtDisplay(w), XtWindow(w), xorGC, curfig);
}
/*-------------------------------------------------------------*/
/*  e n d _ r u b b e r b a n d
 *
 *  End of rubber-band drawing
 */
void end_rubberband(w, data, p_event)
Widget    w;
XtPointer data;
XEvent    *p_event;
{
    int x = p_event->xbutton.x,
        y = p_event->xbutton.y;

/* Draw once at old location (to erase figure) */
    draw_figure(XtDisplay(w), XtWindow(w), xorGC, curfig);

/* Now update end-point and redraw in normal GC */
    figures[curfig].x2 = x;
    figures[curfig].y2 = y;
    draw_figure(XtDisplay(w), XtWindow(w), theGC, curfig);
}
/*-------------------------------------------------------------*/
/*  f i g u r e _ s e l e c t
 *
 *  Callback for "Figure" menu
 */
void figure_select(w, client_data, call_data)
Widget    w;
XtPointer client_data, call_data;
{
/* Set figure type and return */
    figtype = *((int *)client_data);

/* Display a status message in the footer area */
     if(figtype < XtNumber(messages))
        XtVaSetValues(message_area, 
                      XtNstring, messages[figtype],
                      NULL);
}
/*-------------------------------------------------------------*/
/*  h a n d l e _ e x p o s e
 *
 *  Expose event-handler for the drawing area 
 */
void handle_expose(w, p_event, r)
Widget w;
XEvent *p_event;
Region r;
{
    Window  win = XtWindow(w);
    Display *p_display = XtDisplay(w);
    
    if(p_event->xexpose.count == 0) 
    {
        int i;
/* Clear the window and draw the figures in the "figures" array*/
        XClearWindow(p_display, win); 

        if(numfigures > 0)
        {
            for(i=0; i<numfigures; i++)
            {
                draw_figure(p_display, win, theGC, i);
            }
        }
    }
}
/*-------------------------------------------------------------*/
/*  d r a w _ f i g u r e
 *
 *  Draw a specified figure
 */
static void draw_figure(d, w, gc, curfig)
Display *d;
Window  w;
GC      gc;
int     curfig;
{
    int x1 = figures[curfig].x1, y1 = figures[curfig].y1,
        x2 = figures[curfig].x2, y2 = figures[curfig].y2, t;
        
/* Make sure x2 >= x1 and y2 >= y1 */
    if(figures[curfig].type != LINE && x1 > x2)
    {
        t = x1;
        x1 = x2;
        x2 = t;
    }
    if(figures[curfig].type != LINE && y1 > y2)
    {
        t = y1;
        y1 = y2;
        y2 = t;
    }
    switch(figures[curfig].type)
    {
        case LINE:
            XDrawLine(d, w, gc, x1, y1, x2, y2);
            break;
        case RECT:
            XDrawRectangle(d, w, gc, x1, y1, x2-x1, y2-y1);
            break;
        case ELLIPSE:
            XDrawArc(d, w, gc, x1, y1, x2-x1, y2-y1, 0, 360*64);
            break;
        case FILLRECT:
            XFillRectangle(d, w, gc, x1, y1, x2-x1, y2-y1);
            break;
        case FILLELLIPSE:
            XFillArc(d, w, gc, x1, y1, x2-x1, y2-y1, 0, 360*64);
            break;
    }
}
