NAME
    Tk::WorldCanvas - Autoscaling Canvas widget with zoom, viewAll,
    viewArea, viewFit, and center.

SYNOPSIS
        $worldcanvas = $parent->WorldCanvas(?options?);

DESCRIPTION
    This module is a wrapper around the Canvas widget that maps the
    user's coordinate system to the now mostly hidden coordinate
    system of the Canvas widget. In world coordinates the y-axis
    increases in the upward direction.

    *WorldCanvas* is meant to be a drop in replacement for Canvas.

    Most of the *WorldCanvas* methods are the same as the *Canvas*
    methods except that they accept and return world coordinates
    instead of widget coordinates.

INSTALLATION
        Standard method:

        perl Makefile.PL
        make
        make test
        make install

        The last step requires proper permissions.

        Or you can copy the WorldCanvas.pm file to a local directory and
        skip the formalities.

NEW METHODS
    *$worldcanvas*->zoom(*zoom factor*)
        Zooms the display by the specified amount. Example:

            $worldcanvas->CanvasBind('<i>' => sub {$worldcanvas->zoom(1.25)});
            $worldcanvas->CanvasBind('<o>' => sub {$worldcanvas->zoom(0.8)});

            # If you are using the 'Scrolled' constructor as in:
            my $worldcanvas = $main->Scrolled('WorldCanvas', -scrollbars => 'nw', ... )
            # you want to bind the key-presses to the 'worldcanvas' Subwidget of Scrolled.
            my $scrolled_canvas = $worldcanvas->Subwidget('worldcanvas'); # note the lower case 'worldcanvas'
            $scrolled_canvas->CanvasBind('<i>' => sub {$scrolled_canvas->zoom(1.25)});
            $scrolled_canvas->CanvasBind('<o>' => sub {$scrolled_canvas->zoom(0.8)});

            # I don't like the scrollbars taking the focus when I
            # <ctrl>-tab through the windows, so I:
            $worldcanvas->Subwidget('xscrollbar')->configure(-takefocus => 0);
            $worldcanvas->Subwidget('yscrollbar')->configure(-takefocus => 0);

    *$worldcanvas*->center(*x, y*)
        Centers the display around world coordinates x, y. Example:

            $worldcanvas->CanvasBind('<2>' =>
                sub {
                    $worldcanvas->CanvasFocus;
                    $worldcanvas->center($worldcanvas->eventLocation);
                }
            );

    *$worldcanvas*->centerTags([-exact => {0 | 1}], *TagOrID, [TagOrID, ...]*)
        Centers the display around the center of the bounding box
        containing the specified TagOrID's without changing the
        current magnification of the display.

        '-exact => 1' will cause the canvas to be scaled twice to
        get an accurate bounding box. This will be expensive if the
        canvas contains a large number of objects.

    *$worldcanvas*->eventLocation()
        Returns the world coordinates (x, y) of the last Xevent.

    *$worldcanvas*->panWorld(*dx, dy*)
        Pans the display by the specified world distances. panWorld
        is not meant to replace the xview/yview panning methods.
        Most user interfaces will want the arrow keys tied to the
        xview/yview panning methods (the default bindings), which
        pan in widget coordinates.

        If you do want to change the arrow key-bindings to pan in
        world coordinates using panWorld you must disable the
        default arrow key-bindings.

            Example:

            $mainwindow->bind('WorldCanvas',    '<Up>' => "");
            $mainwindow->bind('WorldCanvas',  '<Down>' => "");
            $mainwindow->bind('WorldCanvas',  '<Left>' => "");
            $mainwindow->bind('WorldCanvas', '<Right>' => "");

            $worldcanvas->CanvasBind(   '<Up>' => sub {$worldcanvas->panWorld(0,  100);});
            $worldcanvas->CanvasBind( '<Down>' => sub {$worldcanvas->panWorld(0, -100);});
            $worldcanvas->CanvasBind( '<Left>' => sub {$worldcanvas->panWorld(-100, 0);});
            $worldcanvas->CanvasBind('<Right>' => sub {$worldcanvas->panWorld( 100, 0);});

        This is not usually desired, as the percentage of the
        display that is shifted will be dependent on the current
        display magnification.

    *$worldcanvas*->pixelSize()
        Returns the width (in world coordinates) of a pixel (at the
        current magnification).

    *$worldcanvas*->rubberBand(*{0|1|2}*)
        Creates a rubber banding box that allows the user to
        graphically select a region. rubberBand is called with a
        step parameter '0', '1', or '2'. '0' to start a new box, '1'
        to stretch the box, and '2' to finish the box. When called
        with '2', the specified box is returned (x1, y1, x2, y2)

        The band color is set with the *WorldCanvas* option '-
        bandColor'. The default color is 'red'

        Example, specify a region to delete:

            $worldcanvas->configure(-bandColor => 'purple');
            $worldcanvas->CanvasBind('<3>'               => sub {$worldcanvas->CanvasFocus;
                                                                 $worldcanvas->rubberBand(0)
                                                                });
            $worldcanvas->CanvasBind('<B3-Motion>'       => sub {$worldcanvas->rubberBand(1)});
            $worldcanvas->CanvasBind('<ButtonRelease-3>' => sub {my @box = $worldcanvas->rubberBand(2);
                                                                 my @ids = $worldcanvas->find('enclosed', @box);
                                                                 foreach my $id (@ids) {$worldcanvas->delete($id)}
                                                                });
            # Note: '<B3-ButtonRelease>' will be called for any ButtonRelease!
            # You should use '<ButtonRelease-3>' instead.

            # If you want the rubber band to look smooth during panning and
            # zooming, add rubberBand(1) update calls to the appropriate key-bindings:

            $worldcanvas->CanvasBind(   '<Up>' => sub {$worldcanvas->rubberBand(1);});
            $worldcanvas->CanvasBind( '<Down>' => sub {$worldcanvas->rubberBand(1);});
            $worldcanvas->CanvasBind( '<Left>' => sub {$worldcanvas->rubberBand(1);});
            $worldcanvas->CanvasBind('<Right>' => sub {$worldcanvas->rubberBand(1);});
            $worldcanvas->CanvasBind('<i>' => sub {$worldcanvas->zoom(1.25); $worldcanvas->rubberBand(1);});
            $worldcanvas->CanvasBind('<o>' => sub {$worldcanvas->zoom(0.8);  $worldcanvas->rubberBand(1);});

        This box avoids the overhead of bounding box calculations
        that can occur if you create your own rubberBand outside of
        *WorldCanvas*.

    *$worldcanvas*->viewAll([-border => number])
        Displays at maximum possible zoom all objects centered in
        the *WorldCanvas*. The switch '-border' specifies, as a
        percentage of the screen, the minimum amount of white space
        to be left on the edges of the display. Default '-border' is
        0.02.

        All *WorldCanvas* view methods use the current canvas height
        and width to determine the scale and position of the view
        area. The canvas size will be '1 by 1' until it is
        instantiated by the MainLoop call or the first $MainWindow-
        >update(). So the view methods will produce undesirable
        results if they are called before the canvas has been
        instantiated (and given a size).

        It is common for an application to start by adding objects,
        calling viewAll or viewArea, and then entering MainLoop. A
        $MainWindow->update is needed before the viewAll.

            Example:

            <Add objects to canvas>
            $mw->update;         # Instantiates the canvas (and determines its width and height)
            $worldcanvas->viewAll;
            MainLoop;

    *$worldcanvas*->viewArea(x1, y1, x2, y2, [-border => number]))
        Displays at maximum possible zoom the specified region
        centered in the *WorldCanvas*.

    *$worldcanvas*->viewFit([-border => number], *TagOrID*, [*TagOrID*, ...])
        Adjusts the worldcanvas to display all of the specified
        tags. The '-border' switch specifies (as a percentage) how
        much extra surrounding space should be shown.

    *$worldcanvas*->getView()
        Returns the rectangle of the current view (x1, y1, x2, y2)

    *$worldcanvas*->widgetx(*x*)
    *$worldcanvas*->widgety(*y*)
    *$worldcanvas*->widgetxy(*x, y*)
        Convert world coordinates to widget coordinates.

    *$worldcanvas*->worldx(*x*)
    *$worldcanvas*->worldy(*y*)
    *$worldcanvas*->worldxy(*x, y*)
        Convert widget coordinates to world coordinates.

CHANGED METHODS
        World coordinates are supplied and returned to WorldCanvas
        methods instead of widget coordinates unless otherwise
        specified. (ie. These methods take and return world
        coordinates: center, panWorld, viewArea, find, coords,
        scale, move, bbox, rubberBand, eventLocation, pixelSize, and
        create*)

    *$worldcanvas*->bbox([-exact => {0 | 1}], *TagOrID*, [*TagOrID*, ...])
        '-exact => 1' is only needed if the TagOrID is not 'all'. It
        will cause the canvas to be scaled twice to get an accurate
        bounding box. This will be expensive if the canvas contains
        a large number of objects.

        Neither setting of exact will produce exact results because
        the underlying canvas bbox method returns a slightly larger
        box to insure that everything is contained. It appears that
        a number close to '2' is added or subtracted. The '-exact =>
        1' zooms in to reduce this error.

        If the underlying canvas bbox method returns a bounding box
        that is small (high error percentage) then '-exact => 1' is
        done automatically.

    *$worldcanvas*->scale(*'all', xOrigin, yOrigin, xScale, yScale*)
        Scale should not be used to 'zoom' the display in and out as
        it will change the world coordinates of the scaled objects.
        Methods zoom, viewArea, and viewAll should be used to change
        the scale of the display without affecting the dimensions of
        the objects.

VIEW AREA CHANGE CALLBACK
    *Tk::WorldCanvas* option '-changeView' can be used to specify a
    callback for a change of the view area. This is useful for
    updating a second worldcanvas which is displaying the view
    region of the first worldcanvas.

    The callback subroutine will be passed the coordinates of the
    displayed box (x1, y1, x2, y2). These arguments are added after
    any extra arguments specifed by the user calling 'configure'.

        Example:

        $worldcanvas->configure(-changeView => [\&changeView, $worldcanvas2]);
        # viewAll if worldcanvas2 widget is resized.
        $worldcanvas2->CanvasBind('<Configure>' => sub {$worldcanvas2->viewAll});

        {
            my $viewBox;
            sub changeView {
                my ($canvas2, @coords) = @_;

                $canvas2->delete($viewBox) if $viewBox;
                $viewBox = $canvas2->createRectangle(@coords, -outline => 'orange');
            }
        }

SCROLL REGION NOTES
    (1) The underlying *Tk::Canvas* has a '-confine' option which is
    set to '1' by default. With '-confine => 1' the canvas will not
    allow the display to go outside of the scroll region causing
    some methods to not work accurately. For example, the 'center'
    method will not be able to center on coordinates near to the
    edge of the scroll region; 'zoom out' near the edge will zoom
    out and pan towards the center.

    *Tk::WorldCanvas* sets '-confine => 0' by default to avoid these
    problems. You can change it back with:

        $worldcanvas->configure(-confine => 1);

    (2) '-scrollregion' is maintained by *WorldCanvas* to include
    all objects on the canvas. '-scrollregion' will be adjusted
    automatically as objects are added, deleted, scaled, moved, etc.
    (You can create a static scrollregion by adding a border
    rectangle to the canvas.)

    (3) The bounding box of all objects is required to set the
    scroll region. Calculating this bounding box is expensive if the
    canvas has a large number of objects. So for performance reasons
    these operations will not immediately change the bounding box if
    they potentially shrink it:

        coords
        delete
        move
        scale

    Instead they will mark the bounding box as invalid, and it will
    be updated at the next zoom or pan operation. The only downside
    to this is that the scrollbars will be incorrect until the
    update.

    If these operations increase the size of the box, changing the
    box is trivial and the update is immediate.

AUTHOR
    Joseph Skrovan (*joseph@skrovan.com*)

    Note: based on an earlier implementation by Rudy Albachten
    (*rudy@albachten.com*)

    If you use and enjoy *WorldCanvas* please let me know.

COPYRIGHTS
        Copyright (c) 2002 Joseph Skrovan. All rights reserved.
        This program is free software; you can redistribute it and/or modify it
        under the same terms as Perl itself. 

