/*
 * scroll_cells.c -- scroll a bitmap of cells around in a canvas.
 * The cells are rectangular areas labeled with numbers which may
 * represent arbitrary data such as icon images.  The cell sizes are
 * defined to be 64 by 64 aligned in rows and columns.  This example
 * is used to demonstrate how to configure scrollbars to accommodate
 * arbitrary data within a window.
 */
#include <stdio.h>
#include <X11/X.h>
#include <X11/Xlib.h>   /* Using Xlib graphics */
#include <xview/xview.h>
#include <xview/canvas.h>
#include <xview/scrollbar.h>
#include <xview/font.h>
#include <xview/xv_xrect.h>

#define CELL_WIDTH              64
#define CELL_HEIGHT             64
#define CELLS_PER_HOR_PAGE      5 /* when paging w/scrollbar */
#define CELLS_PER_VER_PAGE      5 /* when paging w/scrollbar */
#define CELLS_PER_ROW           8
#define CELLS_PER_COL           16

Pixmap          cell_map;       /* pixmap copied onto canvas window */
Scrollbar       horiz_scrollbar;
Scrollbar       vert_scrollbar;
GC              gc;             /* General usage GC */

main(argc, argv)
int argc;
char *argv[];
{
    Frame       frame;
    Canvas      canvas;
    void        repaint_proc();

    /* Initialize, create frame and canvas... */
    xv_init(XV_INIT_ARGC_PTR_ARGV, &argc, argv, NULL);

    frame = (Frame)xv_create(XV_NULL, FRAME,
                             FRAME_LABEL,            argv[0],
                             FRAME_SHOW_FOOTER,      TRUE,
                             NULL);

    canvas = (Canvas)xv_create(frame, CANVAS,
                               /* make subwindow the size of a "page" */
                               XV_WIDTH,               CELL_WIDTH * CELLS_PER_HOR_PAGE,
                               XV_HEIGHT,              CELL_HEIGHT * CELLS_PER_VER_PAGE,
                               /* canvas is much larger than the window */
                               CANVAS_WIDTH,           CELL_WIDTH * CELLS_PER_ROW + 1,
                               CANVAS_HEIGHT,          CELL_HEIGHT * CELLS_PER_COL + 1,
                               CANVAS_AUTO_EXPAND,     FALSE,
                               CANVAS_AUTO_SHRINK,     FALSE,
                               /* don't retain window -- we'll need
                                * to repaint it all the time */
                               CANVAS_RETAINED,        FALSE,
                               /* we're using Xlib graphics calls in repaint_proc() */
                               CANVAS_X_PAINT_WINDOW,  TRUE,
                               CANVAS_REPAINT_PROC,    repaint_proc,
                               /* we'll be repainting over exposed areas,
                                * so don't bother clearing */
                               OPENWIN_AUTO_CLEAR,     FALSE,
                               NULL);

    /*
     * Create scrollbars attached to the canvas.  When user clicks
     * on cable, page by the page size (PAGE_LENGTH).  Scrolling
     * should move cell by cell, not by one pixel (PIXELS_PER_UNIT).
     */
    vert_scrollbar = xv_create(canvas, SCROLLBAR,
                               SCROLLBAR_DIRECTION,            SCROLLBAR_VERTICAL,
                               SCROLLBAR_PIXELS_PER_UNIT,      CELL_HEIGHT,
                               SCROLLBAR_OBJECT_LENGTH,        CELLS_PER_COL,
                               SCROLLBAR_PAGE_LENGTH,          CELLS_PER_VER_PAGE,
                               SCROLLBAR_VIEW_LENGTH,          CELLS_PER_VER_PAGE,
                               NULL);
    horiz_scrollbar = xv_create(canvas, SCROLLBAR,
                                SCROLLBAR_DIRECTION,            SCROLLBAR_HORIZONTAL,
                                SCROLLBAR_PIXELS_PER_UNIT,      CELL_WIDTH,
                                SCROLLBAR_OBJECT_LENGTH,        CELLS_PER_ROW,
                                SCROLLBAR_PAGE_LENGTH,          CELLS_PER_HOR_PAGE,
                                SCROLLBAR_VIEW_LENGTH,          CELLS_PER_HOR_PAGE,
                                NULL);

    /*
     * create pixmap and draw cells into it ... this is the abstraction.
     * The cell_map is copied into the window via XCopyPlane in the
     * repaint procedure.
     */
    {
        short           x, y, pt = 0;
        Xv_Font         font;
        XPoint          points[256]; /* keep Xlib calls to a minimum */
        XGCValues       gcvalues;
        Display *dpy = (Display *)xv_get(canvas, XV_DISPLAY);

        font = (Xv_Font)xv_find(frame, FONT,
#ifndef __linux__
                                FONT_NAME,          "icon",
#else
                                FONT_NAME,          "lucidasanstypewriter-24",
#endif
                                NULL);
        cell_map = XCreatePixmap(dpy, DefaultRootWindow(dpy),
                                 CELLS_PER_ROW * CELL_WIDTH + 1,
                                 CELLS_PER_COL * CELL_HEIGHT + 1,
                                 1); /* We only need a 1-bit deep pixmap */

        /* Create the gc for the cell_map -- since it is 1-bit deep,
         * use 0 and 1 for fg/bg values.  Also, limit number of
         * events generated by setting graphics exposures to False.
         */
        gcvalues.graphics_exposures = False;
        gcvalues.foreground = WhitePixel(dpy, DefaultScreen(dpy));

        if(font)
            gcvalues.font = (Font)xv_get(font, XV_XID);

        gc = XCreateGC(dpy, cell_map,
                       GCFont | GCForeground | GCBackground | GCGraphicsExposures,
                       &gcvalues);
        XFillRectangle(dpy, cell_map, gc, 0, 0, CELLS_PER_ROW * CELL_WIDTH + 1,
                       CELLS_PER_COL * CELL_HEIGHT + 1);
        XSetForeground(dpy, gc, 1L);
        XSetBackground(dpy, gc, 0L);

        if(!font)
        {
            /* dot every other pixel */
            for(x = 0; x <= CELL_WIDTH * CELLS_PER_ROW; x += 2)
                for(y = 0; y <= CELL_HEIGHT * CELLS_PER_COL; y += 2)
                {
                    if(x % CELL_WIDTH != 0 && y % CELL_HEIGHT != 0)
                        continue;

                    points[pt].x = x, points[pt].y = y;

                    if(++pt == sizeof points / sizeof points[0])
                    {
                        XDrawPoints(dpy, cell_map, gc, points, pt,
                                    CoordModeOrigin);
                        pt = 0;
                    }
                }

            if(pt != sizeof points)  /* flush remaining points */
                XDrawPoints(dpy, cell_map, gc,
                            points, pt, CoordModeOrigin);
        }

        /* Icon font not available.  Instead, label each cell
         * with a string describing the cell's coordinates.
         */
        for(x = 0; x < CELLS_PER_ROW; x++)
            for(y = 0; y < CELLS_PER_COL; y++)
            {
                char buf[8];

                if(!font)
                {
                    sprintf(buf, "%d,%d", x + 1, y + 1);
                    XDrawString(dpy, cell_map, gc,
                                x * CELL_WIDTH + 5, y * CELL_HEIGHT + 25,
                                buf, strlen(buf));
                }
                else
                {
                    buf[0] = x + y * CELLS_PER_COL;
                    XDrawString(dpy, cell_map, gc,
                                x * CELL_WIDTH, y * CELL_HEIGHT, buf, 1);
                }
            }

        /* we're now done with the cell_map, so free gc and create
         * a new one based on the window that will use it.  Otherwise,
         * the GC may not work because of different depths.
         */
        if(font)
            xv_destroy(font);

        XFreeGC(dpy, gc);
        gcvalues.background = WhitePixel(dpy, DefaultScreen(dpy));
        gcvalues.foreground = BlackPixel(dpy, DefaultScreen(dpy));
        gcvalues.plane_mask = 1L;
        gc = XCreateGC(dpy, DefaultRootWindow(dpy),
                       GCForeground | GCBackground | GCGraphicsExposures, &gcvalues);
    }

    /* shrink frame to minimal size and start notifier */
    window_fit(frame);
    xv_main_loop(frame);
}

/*
 * The repaint procedure is called whenever repainting is needed in
 * a paint window.  Since the canvas is not retained, this routine
 * is going to be called anytime the user scrolls the canvas.  The
 * canvas will handle repainting the portion of the canvas that
 * was in view and has scrolled onto another viewable portion of
 * the window.  The xrects parameter will cover the new areas that
 * were not in view before and have just scrolled into view.  If
 * the window resizes or if the window is exposed by other windows
 * disappearing or cycling through the window tree, then the number
 * of xrects will be more than one and we'll have to copy the new
 * areas one by one.  Clipping isn't necessary since the areas to
 * be rendered are set by the xrects value.
 */
void
repaint_proc(canvas, paint_window, dpy, win, xrects)
Canvas          canvas;
Xv_Window       paint_window;
Display         *dpy;
Window          win;
Xv_xrectlist    *xrects;
{
    int x, y;

    x = (int)xv_get(horiz_scrollbar, SCROLLBAR_VIEW_START);
    y = (int)xv_get(vert_scrollbar, SCROLLBAR_VIEW_START);

    for(xrects->count--; xrects->count >= 0; xrects->count--)
    {
        printf("top-left cell = %d, %d -- %d,%d %d,%d\n", x + 1, y + 1,
               xrects->rect_array[xrects->count].x,
               xrects->rect_array[xrects->count].y,
               xrects->rect_array[xrects->count].width,
               xrects->rect_array[xrects->count].height);

        XCopyPlane(dpy, cell_map, win, gc,
                   xrects->rect_array[xrects->count].x,
                   xrects->rect_array[xrects->count].y,
                   xrects->rect_array[xrects->count].width,
                   xrects->rect_array[xrects->count].height,
                   xrects->rect_array[xrects->count].x,
                   xrects->rect_array[xrects->count].y, 1L);
    }
}
