#include <stdio.h>
#include <sp/spBaseLib.h>
#include <sp/spComponentLib.h>
#include <sp/spComponentMain.h>

#define CANVAS_DEFAULT_WIDTH 500
#define CANVAS_DEFAULT_HEIGHT 500

spComponent canvas = NULL;
spComponent print_canvas = NULL;
spComponent status_bar = NULL;
spComponent show_status_bar_menu = NULL;
spComponent show_status_bar_popup_menu = NULL;

static char hello_string[] = "Hello World";
static char goodby_string[] =  "Goodbye World";
static spBool print_by_copy_image = SP_FALSE;
static spBool do_scaling = SP_TRUE;

static void canvasDraw(spComponent component)
{
    int i;
    int width = 0, height = 0;
    int sx = 0, sy = 0;
    int swidth = 0, sheight = 0;
    static char buf[SP_MAX_LINE];
    static spGraphics graphics = NULL;
    static spGraphics graphics_xor = NULL;

    spDebug(50, "canvasDraw", "in\n");
    
    if (graphics == NULL) {
        graphics = spCreateGraphics("graphics1",
                                    SppForeground, "red",
                                    SppLineType, SP_LINE_DASH_DOT,
                                    SppLineWidth, 1,
                                    SppFontName, "-*-*-bold-*-normal--16-*-*-*-*-*-*-*",
                                    /*SppFontName, "-*-*-heavy-i-normal--48-*-*-*-*-*-*-*",*/
                                    NULL);
        spDebug(50, "canvasDraw", "create graphics1 done\n");
    }

    if (graphics_xor == NULL) {
        graphics_xor = spCreateGraphics("graphics2",
                                        SppForeground, "max",
                                        SppGraphicsMode, SP_GM_XOR,
                                        NULL);
        spDebug(50, "canvasDraw", "create graphics2 done\n");
    }
    
    spGetSize(component, &width, &height);
    spDebug(50, "canvasDraw", "width = %d, height = %d\n", width, height);

    if (component == canvas) {
        sprintf(buf, "width = %d, height = %d", width, height);
        spSetStatusText(status_bar, 0, buf);
    }
    
    spDrawLine(component, graphics, 0, 0, width, height);
    spDrawLine(component, graphics, width, 0, 0, height);
    spFillRectangle(component, graphics, 80, 80, 100, 50);
    spFillRectangle(component, graphics_xor, 40, 40, 100, 50);
    
    spDebug(20, "canvasDraw", "width = %d, height = %d\n", width, height);
    spFillArc(component, graphics, width / 4, height / 4,
              width / 2, height / 2, 0, 45);
    spDrawArc(component, graphics, width / 4, height / 4,
              width / 2, height / 2, 90, 45);
    spFillArc(component, graphics, width / 4, height / 4,
              width / 2, height / 2, 180, 45);
    spDrawArc(component, graphics, width / 4, height / 4,
              width / 2, height / 2, 270, 45);

    if (spGetStringExtent(component, graphics, hello_string,
                          &sx, &sy, &swidth, &sheight, NULL) == SP_TRUE) {
        spDebug(20, "canvasDraw",
                "sx = %d, sy = %d, sw = %d, sh = %d\n",
                sx, sy, swidth, sheight);
        spDrawRectangle(component, graphics, 5 + sx, 50 + sy, swidth, sheight);
    }
    spDrawString(component, graphics, 5, 50, hello_string);

#if 1
    {
        int spos, next_sx;
        
        /* draw rectangle with same size as above rectangle */
        spDrawRectangle(component, graphics, 5 + sx, 50 + sy + (2 * sheight), swidth, sheight);
        
        /* to check spGetStringExtent behavior */
        next_sx = 0;
        spos = 0;
        for (i = 0; hello_string[i] != '\0'; i++) {
            buf[0] = hello_string[i];
            buf[1] = '\0';
            next_sx = 0;
            if (spGetStringExtent(component, graphics, buf,
                                  NULL, NULL, NULL, NULL, &next_sx) == SP_TRUE) {
                /* draw character one by one */
                spDrawString(component, graphics, 5 + spos, 50 + (2 * sheight), buf);
                spos += next_sx;
            }
        }
    }
#endif

    spSetGraphicsParams(graphics, SppForeground, "green", NULL);
    
    spDrawRectangle(component, graphics, 0, 0, width - 1, height - 1);
    
    /* string alignment check */
    {
        int n = 1;
        int hfactor = 4;
        
        if (sheight <= 0) sheight = 20;
        spDrawLine(component, graphics, width - 140, 0, width - 140, height);
        spDrawString(component, graphics, width - 140, 50, hello_string);
        spSetGraphicsParams(graphics, SppStringAlignment, SP_ALIGNMENT_CENTER, NULL);
        spDrawString(component, graphics, width - 140, 50 + sheight * hfactor * n++, hello_string);
        spSetGraphicsParams(graphics, SppStringAlignment, SP_ALIGNMENT_END, NULL);
        spDrawString(component, graphics, width - 140, 50 + sheight * hfactor * n++, hello_string);
        
        spDrawLine(component, graphics, 0, 50 + sheight * hfactor * n, width, 50 + sheight * hfactor * n);
        spSetGraphicsParams(graphics,
                            SppStringRotation, -30,
                            SppStringAlignment, SP_ALIGNMENT_END,
                            SppStringRotationFailedAlignment, SP_ALIGNMENT_END,
                            NULL);
        spDrawString(component, graphics, width - 140, 50 + sheight * hfactor * n, hello_string);
        spSetGraphicsParams(graphics, SppStringRotation, -60, NULL);
        spDrawString(component, graphics, width - 140, 50 + sheight * hfactor * n, hello_string);
        spSetGraphicsParams(graphics, SppStringRotation, -90, NULL);
        spDrawString(component, graphics, width - 140, 50 + sheight * hfactor * n, hello_string);
        spSetGraphicsParams(graphics, SppStringRotation, -120, NULL);
        spDrawString(component, graphics, width - 140, 50 + sheight * hfactor * n++, hello_string);
        
        spDrawLine(component, graphics, 0, 50 + sheight * hfactor * n, width, 50 + sheight * hfactor * n);
        spSetGraphicsParams(graphics,
                            SppStringRotation, -120,
                            SppStringAlignment, SP_ALIGNMENT_CENTER,
                            SppStringRotationFailedAlignment, SP_ALIGNMENT_CENTER,
                            NULL);
        spDrawString(component, graphics, width - 140, 50 + sheight * hfactor * n, hello_string);
        spSetGraphicsParams(graphics, SppStringRotation, -150, NULL);
        spDrawString(component, graphics, width - 140, 50 + sheight * hfactor * n, hello_string);
        spSetGraphicsParams(graphics, SppStringRotation, -180, NULL);
        spDrawString(component, graphics, width - 140, 50 + sheight * hfactor * n, hello_string);
        spSetGraphicsParams(graphics, SppStringRotation, -210, NULL);
        spDrawString(component, graphics, width - 140, 50 + sheight * hfactor * n++, hello_string);
        
        spDrawLine(component, graphics, 0, 50 + sheight * hfactor * n, width, 50 + sheight * hfactor * n);
        spSetGraphicsParams(graphics,
                            SppStringRotation, 15,
                            SppStringAlignment, SP_ALIGNMENT_BEGINNING,
                            SppStringRotationFailedAlignment, SP_ALIGNMENT_BEGINNING,
                            NULL);
        spDrawString(component, graphics, width - 140, 50 + sheight * hfactor * n, hello_string);
        spSetGraphicsParams(graphics, SppStringRotation, 30, NULL);
        spDrawString(component, graphics, width - 140, 50 + sheight * hfactor * n, hello_string);
        spSetGraphicsParams(graphics, SppStringRotation, -315, NULL);
        spDrawString(component, graphics, width - 140, 50 + sheight * hfactor * n++, hello_string);
        
        spSetGraphicsParams(graphics,
                            SppStringRotation, 0,
                            SppStringAlignment, SP_ALIGNMENT_BEGINNING,
                            SppStringRotationFailedAlignment, SP_ALIGNMENT_BEGINNING,
                            NULL);
    }

    if (spGetStringExtent(component, graphics, goodby_string,
                          &sx, &sy, &swidth, &sheight, NULL) == SP_TRUE) {
        spDebug(20, "canvasDraw",
                "sx = %d, sy = %d, sw = %d, sh = %d\n",
                sx, sy, swidth, sheight);
        spDrawRectangle(component, graphics,
                        width - 140 + sx, height - 50 + sy, swidth, sheight);
    }
    spDrawString(component, graphics,
                 width - 140, height - 50, goodby_string);

    /* polygon test */
    {
        spPolygon polygon;
        double r;
        double rcos72, rsin72, rcos36, rsin36;

        r = 30.0;
        rcos72 = r * cos(2.0 * PI / 5.0); rsin72 = r * sin(2.0 * PI / 5.0);
        rcos36 = r * cos(PI / 5.0); rsin36 = r * sin(PI / 5.0);

        if ((polygon = spCreatePolygon(5)) != NULL) {
            spStartPolygonPoint(polygon, width / 2, 0);
            spAddPolygonRelativePoint(polygon, -(int)spRound(rsin36), (int)spRound(r + rcos36));
            spAddPolygonRelativePoint(polygon, (int)spRound(rsin36 + rsin72), -(int)spRound(rcos36 + rcos72));
            spAddPolygonRelativePoint(polygon, -(int)spRound(rsin72 * 2.0), 0);
            spAddPolygonRelativePoint(polygon, (int)spRound(rsin72 + rsin36), (int)spRound(rcos36 + rcos72));
            spEndPolygonPoint(polygon);

            spDrawPolygon(component, graphics, polygon);
            /*spMovePolygon(polygon, (int)spRound(3.0*r), (int)spRound(3.0*r));*/
            spMovePolygon(polygon, -width / 4, height / 2);
            spFillPolygon(component, graphics, polygon);
            spDestroyPolygon(polygon);
        }
    }
    
    spSetGraphicsParams(graphics, SppForeground, "red", NULL);
    
    spDebug(20, "canvasDraw", "draw done\n");

    return;
}

static void canvasCB(spComponent component, void *data)
{
    int canvas_width, canvas_height;
    
    if (component == print_canvas && print_by_copy_image == SP_TRUE) {
        if (spGetClientSize(canvas, &canvas_width, &canvas_height) == SP_TRUE) {
            spDebug(20, "canvasCB", "call spCopyImage: canvas_width = %d, canvas_height = %d\n", canvas_width, canvas_height);
            spCopyImage(canvas, component, 0, 0, canvas_width, canvas_height, 0, 0);
        }
    } else {
        canvasDraw(component);
    }
    
    return;
}

static void canvasKeyPressCB(spComponent component, void *data)
{
    int len;
    int width = 0, height = 0;
    int sx = 0, sy = 0, swidth = 0, sheight = 0;
    int next_sx = 0;
    spBool overflow = SP_FALSE;
    spKeySym key_sym;
    spModifierMask mask;
    static int xpos = 5, ypos = 20;
    static char *buf = NULL;
    static int buf_size = 0;
    static spGraphics graphics = NULL;
    static int prev_sheight = 0;

    if (graphics == NULL) {
        graphics = spCreateGraphics("graphics_string",
                                    SppForeground, "blue",
                                    SppLineWidth, 1,
                                    SppFontName, "-*-*-medium-i-normal--16-*-*-*-*-*-*-*",
                                    /*SppFontName, "-*-*-heavy-i-normal--32-*-*-*-*-*-*-*",*/
                                    NULL);
    }

    if (buf == NULL) {
        buf_size = 128;
        buf = xspAlloc(buf_size, char);
    }
    
    spGetSize(component, &width, &height);
    
    spDebug(20, "canvasKeyPressCB", "key pressed\n");
    if ((len = spGetCallbackKeyString(component, buf, buf_size, &overflow)) >= 0) {
        spDebug(20, "canvasKeyPressCB", "len = %d\n", len);
        if (overflow == SP_TRUE) {
            buf_size = len;
            if (buf != NULL) xspFree(buf);
            buf = xspAlloc(buf_size, char);
            len = spGetCallbackKeyString(component, buf, buf_size, &overflow);
        }
        spDebug(20, "canvasKeyPressCB", "string = %s, len = %d\n", buf, len);

        if (spGetModifierKeyMask(component, &mask)) {
            if (mask & SP_SHIFT_MASK) {
                spDebug(20, "canvasKeyPressCB", "SP_SHIFT_MASK\n");
            }
            if (mask & SP_LOCK_MASK) {
                spDebug(20, "canvasKeyPressCB", "SP_LOCK_MASK\n");
            }
            if (mask & SP_CONTROL_MASK) {
                spDebug(20, "canvasKeyPressCB", "SP_CONTROL_MASK\n");
            }
            if (mask & SP_ALT_MASK) {
                spDebug(20, "canvasKeyPressCB", "SP_ALT_MASK\n");
            }
        }
        
        if (spGetStringExtent(component, graphics, buf,
                              &sx, &sy, &swidth, &sheight, &next_sx) == SP_TRUE) {
            spDebug(20, "canvasKeyPressCB",
                    "sx = %d, sy = %d, swidth = %d, sheight = %d, next_sx = %d, xpos = %d, ypos = %d\n",
                    sx, sy, swidth, sheight, next_sx, xpos, ypos);
            spDrawString(component, graphics, xpos, ypos, buf);
            /*xpos += (swidth + sx);*/
            xpos += next_sx;
            prev_sheight = sheight;

            spRefreshCanvas(component);
        }
        
    } else if (spGetCallbackKeySym(component, &key_sym) == SP_TRUE) {
        spDebug(20, "canvasKeyPressCB", "get key sym\n");
        if (key_sym == SPK_Return) {
            xpos = 5;
            ypos += prev_sheight;
            if (ypos >= height) {
                ypos = 20;
            }
        } else if (key_sym == SPK_Tab) {
            xpos += 20;
        } else if (key_sym == SPK_Escape) {
            spDebug(20, "canvasKeyPressCB", "escape program\n");
            spQuit(0);
        } else if (key_sym == SPK_Prior) {
            spDebug(20, "canvasKeyPressCB", "prior key\n");
        } else if (key_sym == SPK_Next) {
            spDebug(20, "canvasKeyPressCB", "next key\n");
        }
    }
    
    spDebug(20, "canvasKeyPressCB", "done\n");
    
    return;
}

static void canvasKeyReleaseCB(spComponent component, void *data)
{
    spDebug(20, "canvasKeyReleaseCB", "key released\n");

    return;
}

static void canvasWheelCB(spComponent component, void *data)
{
    spCallbackReason reason;
    double delta_x, delta_y;
    double h_factor, v_factor;

    reason = spGetCallbackReason(component);
    
    spDebug(20, "canvasWheelCB", "wheel: reason = %d\n", reason);

    if (reason == SP_CR_ZOOM) {
        spGetParams(component,
                    SppHorizontalContentFactor, &h_factor,
                    SppVerticalContentFactor, &v_factor,
                    NULL);
        spDebug(20, "canvasWheelCB", "h_factor = %f, v_factor = %f\n", h_factor, v_factor);
    } else {
        if (spGetCallbackWheelValue(component, &delta_x, &delta_y) == SP_TRUE) {
            spDebug(20, "canvasWheelCB", "delta_x = %f, delta_y = %f\n", delta_x, delta_y);
        }
    }

    return;
}

static void showStatusBarCB(spComponent component, void *data)
{
    spBool set;
    
    if (spGetToggleState(component, &set)) {
        spDebug(20, "showStatusBarCB", "set = %d\n", set);
        if (set == SP_TRUE) {
            spMapComponent(status_bar);
        } else {
            spUnmapComponent(status_bar);
        }
        
        spSetToggleState(show_status_bar_menu, set);
        spSetToggleState(show_status_bar_popup_menu, set);
    }
    
    return;
}

static void changeCursorCB(spComponent component, spCursorType cursor_type)
{
    spCursor cursor;
    
    if (canvas == NULL) return;

    if (cursor_type == SP_CURSOR_UNKNOWN) {
        spUnsetCanvasCursor(canvas);
    } else {
        if ((cursor = spGetCursor(cursor_type)) != NULL) {
            spSetCanvasCursor(canvas, cursor);
            spDestroyCursor(cursor);
        } else {
            spUnsetCanvasCursor(canvas);
        }
    }

    spDebug(20, "changeCursorCB", "done\n");
    
    return;
}

static void pageSetupCB(spComponent component, spComponent pcanvas)
{
    spBool flag;
    
    if (pcanvas == NULL) {
        spDisplayError(component, NULL, "Printing functions are not supported.");
        return;
    }
    
    flag = spOpenDrawingSetupDialog(pcanvas, NULL);
    
    spDebug(20, "pageSetupCB", "done: flag = %d\n", flag);
    return;
}

static void printCB(spComponent component, spComponent pcanvas)
{
    spBool flag = SP_FALSE;
    int w, h;
    int canvas_width, canvas_height;
    double factor, xfactor, yfactor;
    unsigned long caps;

    spDebug(-50, "printCB", "in\n");
    
    if (pcanvas == NULL) {
        spDisplayError(component, NULL, "Printing functions are not supported.");
        return;
    }
    
    if (spOpenDrawingTargetDialog(pcanvas, NULL) == SP_TRUE) {
        caps = spGetDrawingCaps(pcanvas);
        spDebug(20, "printCB", "line: %ld\n", !!(caps & SP_DRAWING_CAPS_DRAW_LINE));
        spDebug(20, "printCB", "wide line: %ld\n", !!(caps & SP_DRAWING_CAPS_DRAW_WIDE_LINE));
        spDebug(20, "printCB", "styled line: %ld\n", !!(caps & SP_DRAWING_CAPS_DRAW_STYLED_LINE));
        spDebug(20, "printCB", "fill rectangle: %ld\n", !!(caps & SP_DRAWING_CAPS_FILL_RECTANGLE));
        spDebug(20, "printCB", "draw rectangle: %ld\n", !!(caps & SP_DRAWING_CAPS_DRAW_RECTANGLE));
        spDebug(20, "printCB", "wide rectangle: %ld\n", !!(caps & SP_DRAWING_CAPS_DRAW_WIDE_RECTANGLE));
        spDebug(20, "printCB", "styled rectangle: %ld\n", !!(caps & SP_DRAWING_CAPS_DRAW_STYLED_RECTANGLE));
        spDebug(20, "printCB", "fill arc: %ld\n", !!(caps & SP_DRAWING_CAPS_FILL_ARC));
        spDebug(20, "printCB", "draw arc: %ld\n", !!(caps & SP_DRAWING_CAPS_DRAW_ARC));
        spDebug(20, "printCB", "wide arc: %ld\n", !!(caps & SP_DRAWING_CAPS_DRAW_WIDE_ARC));
        spDebug(20, "printCB", "styled arc: %ld\n", !!(caps & SP_DRAWING_CAPS_DRAW_STYLED_ARC));
        spDebug(20, "printCB", "fill polygon: %ld\n", !!(caps & SP_DRAWING_CAPS_FILL_POLYGON));
        spDebug(20, "printCB", "draw polygon: %ld\n", !!(caps & SP_DRAWING_CAPS_DRAW_POLYGON));
        spDebug(20, "printCB", "wide polygon: %ld\n", !!(caps & SP_DRAWING_CAPS_DRAW_WIDE_POLYGON));
        spDebug(20, "printCB", "styled polygon: %ld\n", !!(caps & SP_DRAWING_CAPS_DRAW_STYLED_POLYGON));
        spDebug(20, "printCB", "copy image stretchable: %ld\n", !!(caps & SP_DRAWING_CAPS_COPY_IMAGE_STRETCHABLE));
        spDebug(20, "printCB", "mono only: %ld\n", !!(caps & SP_DRAWING_CAPS_MONO_ONLY));
        spDebug(20, "printCB", "string rotation: %ld\n", !!(caps & SP_DRAWING_CAPS_STRING_ROTATION));
        spDebug(20, "printCB", "scaling: %ld\n", !!(caps & SP_DRAWING_CAPS_SCALING));
        
        if (print_by_copy_image == SP_TRUE && canvas != NULL) {
            spGetClientSize(canvas, &canvas_width, &canvas_height);
            spDebug(-20, "printCB", "image size = %f, %f\n", canvas_width, canvas_height);
        } else {
            canvas_width = CANVAS_DEFAULT_WIDTH;
            canvas_height = CANVAS_DEFAULT_HEIGHT;
        }

        spDebug(-20, "printCB", "do_scaling = %d\n", do_scaling);
        
        spSetScalingFactor(pcanvas, 1.0, 1.0);
        
        if (do_scaling == SP_TRUE && spGetClientSize(pcanvas, &w, &h) == SP_TRUE) {
            xfactor = (double)w / (double)canvas_width;
            yfactor = (double)h / (double)canvas_height;
            factor = MIN(xfactor, yfactor);
            spDebug(-20, "printCB", "w = %d, h = %d, factor = %f, xfactor = %f, yfactor = %f\n",
                    w, h, factor, xfactor, yfactor);
#if 0
            spSetScalingFactor(pcanvas, factor, factor);
#else
            spSetScalingFactor(pcanvas, xfactor, yfactor);
#endif
        } else {
            spDebug(-20, "printCB", "no scaling\n");
        }
        
        flag = spRedrawCanvas(pcanvas);
        spDebug(-20, "printCB", "spRedrawCanvas: flag = %d\n", flag);

        spCloseDrawingTarget(pcanvas);
    } else {
        spDebug(-50, "printCB", "spOpenDrawingTargetDialog failed\n");
    }
    
    spDebug(-50, "printCB", "done\n");
    
    return;
}

void checkCB(spComponent component, spBool *flag)
{
    spGetToggleState(component, flag);
    return;
}

int spMain(int argc, char *argv[])
{
    spTopLevel toplevel;
    spComponent frame;
    spComponent menu, menu_bar;
    int status_sizes[] = {200, 0};

    /*spSetDebugLevel(100);*/

    /* initialize toolkit */
    toplevel = spInitialize(&argc, &argv, NULL);
    
    /* create main window */
    frame = spCreateMainFrame("Canvas", NULL);

    /* create status bar */
    status_bar = spCreateStatusBar(frame, "statusBar",
                                   SppItemSizes, status_sizes,
                                   SppHelpItemIndex, 1,
                                   SppUseLastItem, SP_TRUE,
                                   NULL);
    spDebug(50, "spMain", "spCreateStatusBar done\n");

#if 1
    print_canvas = spCreatePrintCanvas(frame,
                                       SppDrawFunc, canvasCB,
                                       NULL);
#else
    print_canvas = spCreatePluginCanvas(frame, SP_DRAWING_PLUGIN_PS/*SP_DRAWING_PLUGIN_EMF*/,
                                        SppDrawFunc, canvasCB,
                                        NULL);
#endif
    spDebug(50, "spMain", "spCreatePluginCanvas done\n");

    /* create menu bar */
    menu_bar = spCreateMenuBar(frame, "menuBar", NULL);
    
    /* create menu */
    menu = spCreatePulldownMenu(menu_bar, "File",
                                SppDescription, "File menu",
                                NULL);
    
    /* add menu item */
    show_status_bar_menu = spAddCheckBoxMenuItem(menu, "Show Status Bar",
                                                 SppCallbackFunc, showStatusBarCB,
                                                 SppSet, SP_TRUE,
                                                 SppShortcut, "Backspace",
                                                 SppDescription, "Show status bar",
                                                 NULL);
    spAddMenuSeparator(menu, "fileMenuSeparator1", NULL);

    /* add print-related menu item */
    spAddCheckBoxMenuItem(menu, "scalingMenuItem",
                          SppTitle, "Enable Scaling",
                          SppCallbackFunc, checkCB,
                          SppCallbackData, &do_scaling,
                          SppSet, do_scaling,
                          SppDescription, "Enable scaling",
                          NULL);
    spAddCheckBoxMenuItem(menu, "printByCopyImageMenuItem",
                          SppTitle, "Enable Printing by Copy Image",
                          SppCallbackFunc, checkCB,
                          SppCallbackData, &print_by_copy_image,
                          SppSet, print_by_copy_image,
                          SppDescription, "Enable printing by copy image",
                          NULL);
    spAddMenuItem(menu, "pageSetupMenuItem",
                  SppTitle, "Page Set&up...",
                  SppCallbackFunc, pageSetupCB,
                  SppCallbackData, print_canvas,
                  SppDescription, "Open page setup dialog",
                  NULL);
    spAddMenuItem(menu, "printMenuItem",
                  SppTitle, "&Print...",
                  SppCallbackFunc, printCB,
                  SppCallbackData, print_canvas,
                  SppDescription, "Print window content",
                  SppShortcut, "A-p",
                  NULL);
    spAddMenuSeparator(menu, SP_QUIT_MENU_SEPARATOR_NAME, NULL);
    
    /* add quit menu */
    spAddMenuItem(menu, SP_QUIT_MENU_ITEM_NAME,
                  SppCallbackFunc, spQuitCB,
                  SppDescription, "Quit program",
                  NULL);

    /* create menu */
    menu = spCreatePulldownMenu(menu_bar, "Cursor",
                                SppDescription, "Cursor menu",
                                NULL);
    
    /* add radio button item */
    spAddRadioButtonMenuItem(menu, "Default",
                             SppCallbackFunc, changeCursorCB,
                             SppCallbackData, SP_CURSOR_UNKNOWN,
                             SppSet, SP_TRUE,
                             NULL);
    spAddRadioButtonMenuItem(menu, "Arrow",
                             SppCallbackFunc, changeCursorCB,
                             SppCallbackData, SP_CURSOR_ARROW,
                             NULL);
    spAddRadioButtonMenuItem(menu, "Text",
                             SppCallbackFunc, changeCursorCB,
                             SppCallbackData, SP_CURSOR_TEXT,
                             NULL);
    spAddRadioButtonMenuItem(menu, "Wait",
                             SppCallbackFunc, changeCursorCB,
                             SppCallbackData, SP_CURSOR_WAIT,
                             NULL);
    spAddRadioButtonMenuItem(menu, "Cross",
                             SppCallbackFunc, changeCursorCB,
                             SppCallbackData, SP_CURSOR_CROSS,
                             NULL);
    spAddRadioButtonMenuItem(menu, "Hand",
                             SppCallbackFunc, changeCursorCB,
                             SppCallbackData, SP_CURSOR_HAND,
                             NULL);
    spAddRadioButtonMenuItem(menu, "Move",
                             SppCallbackFunc, changeCursorCB,
                             SppCallbackData, SP_CURSOR_MOVE,
                             NULL);
    spAddRadioButtonMenuItem(menu, "Size",
                             SppCallbackFunc, changeCursorCB,
                             SppCallbackData, SP_CURSOR_SIZE,
                             NULL);
    spDebug(50, "spMain", "create menus done\n");
    
    /* create canvas */
    canvas = spCreateCanvas(frame, "canvas",
                            CANVAS_DEFAULT_WIDTH, CANVAS_DEFAULT_HEIGHT,
                            SppBorderOn, SP_TRUE,
                            SppDrawBackground, SP_TRUE,
                            SppFocusable, SP_TRUE,
                            SppUseTabKey, SP_TRUE,
                            SppAcceptWheelEvent, SP_TRUE,
                            SppCallbackFunc, canvasCB,
                            NULL);
    spAddCallback(canvas, SP_KEY_PRESS_CALLBACK, canvasKeyPressCB, NULL);
    spAddCallback(canvas, SP_KEY_RELEASE_CALLBACK, canvasKeyReleaseCB, NULL);
    spAddCallback(canvas, SP_WHEEL_CALLBACK, canvasWheelCB, NULL);
    spAddCallback(canvas, SP_ZOOM_CALLBACK, canvasWheelCB, NULL);
    spDebug(50, "spMain", "create canvas done\n");

    /* create popup menu */
    menu = spCreatePopupMenu(canvas, "popupMenu", NULL);
    
    /* add menu item */
    show_status_bar_popup_menu = spAddCheckBoxMenuItem(menu, "Show Status Bar",
                                                       SppCallbackFunc, showStatusBarCB,
                                                       SppSet, SP_TRUE,
                                                       SppDescription, "Show status bar",
                                                       SppShortcut, "A-s",
                                                       NULL);
    
    /* add menu item */
    spAddMenuItem(menu, "Quit",
                  SppCallbackFunc, spQuitCB,
                  SppDescription, "Quit program",
                  SppShortcut, "A-q",
                  NULL);
    spDebug(50, "spMain", "create popup menu done\n");
    
    /* popup window */
    spPopupWindow(frame);
    
    /* main loop */
    return spMainLoop(toplevel);
}
