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 replacement for Canvas. It's not quite a "drop in" replacement though because the y-axis is inverted
    compared to Canvas. Usually to convert you will have to invert all y-coordinates used to create objects. Typically, you should call
    $worldcanvas->viewAll (or $worldcanvas->viewArea(@box)) before calling MainLoop.

    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.

    *$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 specified 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. 

