9—
The Canvas Widget
0181-01.gif
The canvas widget is mainly used for drawing items such as arcs, lines, rectangles, circles, and so on. You can also place text and other widgets inside a canvas widget. Think of it as a painter's canvas: It is blank until you decide to draw something on it. But unlike a painter's canvas, which is limited in size, this canvas is scrollable in any direction. Here are some examples of how you can use a canvas widget:
• Create a drawing program.
• Display a graph based on input from the user.
• Create a customized slider.
Each item you create in a canvas widget can have bindings attached to it to allow for easy  interaction with the user.
Creating a Canvas
I recommend that you always use the Scrolled method to create a canvas unless you know for sure that your canvas is going to be a fixed size that will fit in the window:
$canvas = $mw->Canvas( [ option => values, ... ] )->pack();
# or...
$canvas = $mw->Scrolled('Canvas', [ option => values, ... ])->pack();
The first line creates just a canvas and the second creates a canvas with scrollbars. (See Chapter 6, Scrollbars, for more information on what else you can do with the Scrolled method.) To create a canvas widget, use the desired parent widget to
invoke the Canvas method and pass any initial options in with their values. The Canvas method returns a reference to the newly created canvas widget.
Before we get into the options and methods available with a canvas widget, here are a few miscellaneous things you should know about using a canvas widget.
Coordinate System
A canvas widget uses a coordinate system to locate items inside of it, but the coordinate system isn't a normal one. It's more like an upside-down coordinate system.
Figure 9-1 shows a diagram that demonstrates the coordinate system a canvas widget uses.
0182-01.gif
Figure 9-1.
Canvas coordinate system
The x coordinates behave normally; the larger coordinates are to the right and the smaller ones are to the left. The y coordinates look like they have been drinking vodka; the larger y coordinates are on the bottom rather than on the top because the 0,0 point is in the upper-left corner. Although it is rare, you can use negative coordinates in a canvas.
The coordinate system isn't too hard to deal with once you realize what is happening, but if you try to draw a building with a standard coordinate system in mind (that is, with the larger y coordinates higher up), your building will come out upside down.
There are several ways to deal with this. First, adjust your way of thinking so you always think y coordinates are larger at the bottom (never mind all those years we all struggled through geometry classes). Or, you are just as stubborn as I am, you can think in normal coordinates, and have your program do a quick little calculation before sending y coordinates to the canvas functions. (Multiply all y coordinates by -1. Tricky, huh?)
Whichever way you decide to deal with it, be consistent and make sure you comment your code.
The x and y coordinates can be specified in any valid screen unit. They are pixels by default. If you follow the coordinate number with a letter m, then you are measuring distance in millimeters. The other letters you can use are p for printer points, i for inches, and c for centimeters. The default is pixels, which is what we'll use for all of the examples in this chapter.
The Scrollable Region
The scrollable area is the portion of the canvas widget that you want the user to be able to see. If you don't create a scrollable area (by using the -scrollregion option), the user can scroll infinitely in any direction and the scrollbars don't reflect where items on the canvas are.
Figure 9-2 shows an example of the scrollable area compared with the area that is visible in the canvas. If these two areas are the same size, you don't need scrollbars on the canvas (if you use scrollbars, their sliders will completely fill the trough area).
0183-01.gif
Figure 9-2.
Scrollable area compared with visible area
The arrows on the axis markers in Figure 9-2 indicate that the canvas can still be larger than the indicated scrolling area. For instance, if you decide to insert a circle beyond the scrolling area, you have to adjust the scrollable area so the user will be able to see the new circle.
The best way to do this is to use the bbox method, which returns a bounding box for all items that match the tags you send it. Here's what the code looks like:
$canvas->configure(-scrollregion => [ $canvas->bbox("all") ]);
Calling this after you add or remove items to the canvas resets the scroll region to where it needs to be. Of course, if you are adding many different items all at once, you should wait until after you have added them all and then update the scroll region.
Using Bind with a Canvas
When you try to use the bind method with a canvas widget, you'll run into some unexpected problems. You'll either get an error and your script won't run, or your script will run but your bind won't seem to have any effect. In order to get around this, you'll need to use the explicit Tk::bind instead of just bind (because the canvas has its own bind method that you have to avoid using):
$canvas = $mw->Canvas();
$canvas->Tk::bind("<Button-1>", sub { print "bind!\n"; });
You can also use SUPER::bind instead of Tk::bind. Either way will work.*
If you used the Scrolled method to create your canvas, you'll have an added difficulty; you'll have to use the Subwidget method to get to the canvas widget:
$canvas = $mw->Scrolled("Canvas");
$real_canvas = $canvas->Subwidget("canvas");
$real_canvas->Tk::bind("<Button-1>", sub { print "bind!\n" });
Other than this one small annoyance, bind works just as you would expect it would. Here's a quick (and fairly useful) example that will print out the coordinate you clicked on:
$c = $mw->Scrolled("Canvas")->pack();
$canvas = $c->Subwidget("canvas");
$canvas->Tk::bind("<Button-1>", [ \&print_xy, Ev('x'), Ev('y') ]);
sub print_xy {
  my ($canv, $x, $y) = @_;
  print "(x,y) = ", $canv->canvasx($x), ", ", $canv->canvasy($y), "\n";
}
This example prints out the coordinates (in canvas coordinates) when you click the left mouse button.
Canvas Options
The options listed in this section affect the entire canvas widget and the items within it. Items are circles, lines, rectangles, text, or other widgets. These options act as you would expect them to (as explained in Chapter 3, The Basic Button, for
* For those using Tk8.0: You can use canvasBind instead of Tk::bind. I'll refer to Tk::bind throughout the rest of the chapter, but note that you should use canvasBind instead.
most options and in Chapter 6 for the scrollbar options): -background, -borderwidth, -cursor, -height, -highlightbackground, -highlightcolor, -highlightthickness, -relief, -takefocus, -width, -xscrollcommand, and -yscrollcommand.
New Options
When selecting items in the canvas with the mouse cursor, the canvas widget does calculations to determine if the mouse cursor is inside or outside the item. The -closeenough option controls how close the mouse must be to the item before it is considered inside the item. The default value for -closeenough is "1.0", which is 1.0 pixels away. Any floating point number is a valid value (and will always be in pixels) for -closeenough.
I discussed the -scrollregion option briefly in "The Scrollable Region" earlier in this chapter. It takes a list reference, and that list must contain  four coordinates. The coordinates indicate a bounding region for the scrollable area in the canvas. The coordinates are in this order: [ minx, miny, maxx, maxy ]. You can also think of the coordinates as if they were defining the [ left, top, right, bottom ] edges of the scrollable region.
Normally, the canvas widget limits the user to seeing only the area defined by the -scrollregion option. You can allow the user to scroll beyond this area by using -confine => 0. The default for -confine is 1.
Additional Scrolling Options
The -xscrollcommand and -yscrollcommand options both work as described in Chapter 6, but there are two additional options that affect how the canvas scrolls its  contents: -xscrollincrement and -yscrollincrement. Each option takes a valid screen distance for a value. This distance is the unit the canvas will use to scroll in the associated direction. For instance, if you specify -xscrollincrement => 10, each time you click an arrow on the horizontal scrollbar, the contents of the canvas will shift so that the left edge of the contents is an even multiple of 10. Essentially, the canvas will shift the contents 10 pixels in the arrow's direction.
If the value associated with -xscrollincrement or -yscrollincrement is 0 or less, scrolling is done in normal increments.
Options for Text Items
The following options are applied to the entire canvas widget, but they really only affect the text items inside the canvas widget: -insertbackground, -insertborderwidth, -insertofftime, -insertontime, -insertwidth, -selectbackground, -selectborderwidth, and -selectforeground. These options work  the same as they would for an entry widget or a text widget. See Chapter 5, Label and Entry Widgets, and Chapter 8, The Text Widget, for more details.
Canvas Widget Option List
These options all are used with the Canvas method:
-background => color
Sets the background of the canvas to color.
-borderwidth => amount
Changes the width of the edges of the canvas to amount.
-closeenough => float_amount
Sets the amount of distance from the item when the cursor is considered inside the item.
-confine => 1 | 0
Indicates that the canvas will limit itself to the area defined by -scrollregion if set to 1. Default is 1.
-cursor => cursorname
Indicates that the cursor will change to cursorname when it is over the canvas.
-height => amount
Sets the height of the canvas to amount.
-highlightbackground => color
Sets the color the highlight rectangle should be when the canvas does not have the keyboard focus.
-highlightcolor => color
Sets the color the highlight rectangle should be when the canvas does have the keyboard focus.
-highlightthickness => amount
Sets the highlight rectangle. Default is 2.
-insertbackground => color
Sets the color of the area behind the text insert cursor.
-insertborderwidth => amount
Sets the width of the borders on the insert cursor.
-insertofftime => milliseconds
Sets the amount of time the cursor disappears from the screen when it is blinking off.
-insertontime => milliseconds
Sets the amount of time the cursor appears on the screen when it is blinking on.
-insertwidth => amount
Sets the width of the insert cursor.
-relief => "flat" | 'groove' | 'raised' | 'ridge' | 'sunken' | 'solid'
Indicates the way the edges of the canvas are drawn. Default is 'flat'.
-scrollregion => [ left, top, right, bottom ]
Defines the area the user is allowed to scroll.
-selectbackground => color
Sets the color of the area behind any selected text.
-selectborderwidth => amount
Sets the width of the border of the selected area.
-selectforeground => color
Sets the color of the selected text.
-takefocus => 0 | 1 | undef
Determines whether or not the canvas can get keyboard focus. Default is for the application to decide.
-width => amount
Sets the width of the canvas to amount.
-xscrollcommand => callback
Determines the callback used when the canvas is scrolled horizontally (automatically set to the correct callback when the
Scrolled method is used).
-xscrollincrement => amount
Sets the distance the canvas contents move when the arrow on the horizontal scrollbar is clicked.
-yscrollcommand => callback
Determines the callback used when the canvas is scrolled vertically.
-yscrollincrement => amount
Sets the distance the canvas contents move when the arrow on the vertical scrollbar is clicked.
Creating Items in a Canvas
The whole point of having a canvas is to put items in it. You can create arcs, bitmaps, images, lines, rectangles, ovals (circles), polygons, text, and widgets. Each has an associated create XXX method, where the type of item you want to create  replaces the XXX. All of the create methods return a unique ID, which can be used to refer to the item later. When you see a method that takes a tag or an ID as an argument, the ID is the one returned from the create method.
The Arc Item
When you create an arc, you specify a bounding rectangle with two sets of x and y coordinates. The arc is drawn within the confines of the bounding box. Additional options that will change how the arc is drawn in the canvas are explained shortly. The basic createArc statement is as follows:
$id = $canvas->createArc(x1, y1, x2, y2,);
Any additional options used in the createArc method are specified after the coordinates:
$id = $canvas->createArc(x1, y1, x2, y2, option => value);
Each option for the arc item can be used later with the itemcget and itemconfigure canvas methods. The options are:
-extent => degrees
The length of the arc is specified in degrees by using the
-extent option. The default -extent (or length) is 90 degrees. The arc is drawn from the starting point (see -start option) counterclockwise within the rectangle defined by (x1, y1) and (x2, y2). The degrees value should be between -360 and 360. If it is more or less, then the value used is the specified number of degrees modulo 360.
Here are some examples of the -extent option:
# This draws half of an oval
$canvas->createArc(0,0,100,150, -extent => 180);
# This will draw _ of an oval
$canvas->createArc(0,0,100,150, -extent => 270);
-fill => color
To fill the arc with the specified color. By default, there is no fill color for an arc.
-outline => color
Normally the arc is drawn with a black outline. To change the default, use the
-outline option. The outline color is separate from the fill color, so to make it a completely solid object, make the color for -outline and -fill the same.
-outlinestipple => bitmap
To use
-outlinestipple, you must also use the -outline option. Normally, the outline of the arc is drawn solid. Use a bitmap with -outlinestipple to make the outline nonsolid; the specified bitmap pattern will be used to draw the outline of the arc.
-start => degrees
The value associated with the
-start option determines where Per1/Tk starts drawing the arc. The default start position is at three o'clock (0 degrees). The degrees specified are added to this position, but in a counterclockwise direction. Use -start => 90 to make the arc start at the twelve o'clock position, use -start => 180 to make the arc start at the nine o'clock position, and so on.
-stipple => bitmap
The
-stipple option causes the arc to be filled with a bitmap pattern, but only if the -fill option has been specified as well.
-style => "pieslice" | "chord" | "arc"
The -style of the arc determines how the arc is drawn. The default, "pieslice", draws the arc and two lines from the center of the oval ends of the arc segment. The "chord" value draws the arc and a line connecting the two end points of the arc segment. The "arc" value draws just the arc portion with no other lines. The -fill and -stipple options are ignored if "arc" is used.
-tags => taglist
When you create an arc, you use the
-tags option to assign tag names to it. The value associated with -tags is an anonymous list of tag names; for example:
$canvas->createArc(0,0,10,140, -tags => ["arc", "tall"]);
You don't need to use an anonymous list if you are only specifying one tag name:
$canvas->createArc(0,0,10,140, -tags => "arc");
-width => amount
The width of the outline is specified by using
-width. The default -width is 1.
The Bitmap Item
A canvas widget can display a bitmap instead of text just as a button or label can. You can use createBitmap to insert a bitmap into your canvas widget:
$id = $canvas->createBitmap(x, y);
Of course, you must use the -bitmap option to specify which bitmap to display or you won't see anything. So we really create a bitmap like this:
$id = $canvas->createBitmap(x, y, -bitmap => bitmap);
Why they didn't just make the bitmap the third argument, I don't know. That's just the way it is. The other captions available for createBitmap are:
-anchor => "center" | "n" | "e" | "s" | "w" | "ne" | "nw" | "se" | "sw"
The -anchor option determines how the bitmap is placed on the canvas relative to the x,y coordinates indicated. The default for -anchor is "center", which puts the center of the image at the x,y coordinates. Using a single cardinal direction (for example, "e") would place the center of that edge at the x,y coordinates.
-background => color
The
-background option specifies the color to use for all the 0 (zero) bitmap pixels. If you don't specify a background color or use an empty string (" "), the 0 pixels willbe transparent.
-bitmap => bitmapname
You must use the
-bitmap option to tell the canvas which bitmap to display. You can use the built-in bitmaps such as 'info' or 'warning' just as you can with the button widget, or you can specify a filename. Remember, to specify a bitmap file, use an @ sign in front of the bitmap filename.
-foreground => color
The foreground color of a bitmap is the opposite of the background color. (By definition, bitmaps can only have two colors.) The
-foreground option will color all the 1 pixels with this color. The default for -foreground is black.
-tags => taglist
When you create a bitmap, you can assign tag names to it by using the
-tags option. The value associated with -tags is an anonymous list of tag names; for example:
$canvas->createBitmap(0,0, -bitmap => 'info',
                      -tags => ["info", "bitmap"]);
You don't need to use the list if you are only specifying one tag name:
$canvas->createBitmap(0,0, -bitmap => 'info', -tags => "bitmap");
The Image Item
If we can create a bitmap on a canvas, it makes sense that we can create an image as well. We can do so with the createImage method:
$id = $canvas->createImage(x, y,-image => image);
Again, you have to specify an image to display or you won't see anything. The other options available for createImage are:
-anchor => "center""n"  |  "e"  |  "s"  | "w"  |  "ne"  |  "nw"  |  "se"  |  "sw"
The -anchor option for an image works the same as it does for a bitmap. The -anchor option is how the image is positioned around the x,y coordinates. The default for -anchor is 'center'.
-image => $image
The -image option indicates which image to display. The image value is actually a reference to an image created with Photo or Bitmap methods. (See Chapter 3 for more information on how to specify an image file.)
-tags => taglist
Use the
-tags option to assign tag names to an image. The value associated with -tags is an anonymous list of tag names; for example:
$canvas->createImage(0,0, -image => $imgptr,
                     -tags => ["image", "blue"]);
You don't need to use the list if you are only specifying one tag name:
$canvas->createImage(0,0, -image => $imgptr, -tags => "image");
The Line Item
The creatLine method can actually create multiple connected lines, not just one. The first two coordinate sets you supply create the first line, and any additional coordinates will continue the line to that point:
$id = $canvas->createLine(0,0, 400,400);           # creates one line
$id = $canvas->createLine(0,0, 400,400, -50, 240); # creates two lines
After the coordinates, you can specify any options and values you wish to configure the line(s); the options and values are as follows:
-arrow => "none" | "first" | "last" | "both"
You can place arrowheads at either end of the line (or both) by using the -arrow option. If you have more than one line in your createLine method, only the first and/or last point can be made into an arrow. If you want each line to have an arrowhead, then use multiple createLine statements.
-arrowshape => [ dist1, dist2, dist3 ]
The
-arrowshape option only applies if you use the -arrow option as well. Figure 9-3 shows what the distance values mean.
Specify the three distances by using an anonymous list such as this:
$canvas->createLine(10, 10, 200, -40, -arrow => "both",
                    -arrowshape => [20, 20, 20]);
0192-01.gif
Figure 9-3.
Definition of arrowhead
-capstyle => "butt" | "projecting" | "round"
Instead of arrowheads, you can make the ends of the line have one of these styles.
-fill => color
The
-fill option is misnamed because it isn't actually filling anything. The line is simply drawn with this color instead of black.
-joinstyle => "bevel" | "miter" | "round"
The -joinstyle option affects how multiple lines are joined together. The default is "miter". If there is only one line created, this option has no effect.
-smooth => 1 | 0
If -smooth has a value of 1, then, using Bezier spline(s), the line(s) will be drawn as a curve. The first two lines make the first spline, the second and third line make up the second spline, and so on. To make a straight line, repeat the end points of the desired straight line (or use createLine again to make a separate line).
-splinesteps => count
When you use the
-smooth option, the more -splinesteps you use, the smoother the curve. To find out how many steps create the desired effect, you'll have to experiment with different values.
-stipple => bitmap
To have the line drawn with a bitmap pattern (1s in the bitmap have color, 0s are transparent), use the
-stipple option. The bitmap can be a default bitmap name or a filename. The wider the line (see -width), the more the stipple design will show up.
-tags => taglist
When you create a line (or lines), assign tag names to them by using the
-tags option. The value associated with -tags is an anonymous list of tag names; for example:
$canvas->createLine(0,0, 100,100, -tags => ["line", "blue"]);
You don't need to use a list if you are only specifying one tag name:
$canvas->createLine(0,0, 100, 100, -tags => "line");
-width => amount
You can make the line(s) thicker by using the
-width option. Normally the line is drawn only 1 pixel wide. The amount can be any valid screen distance (e.g., centimeters, inches).
The Oval Item
An oval can be a circle if you draw it just right. To create a circle/oval, use the createOval method and specify two sets of points that indicate a rectangle (or square) in which to draw the circle/oval. Here is a simple example:
$id = $canvas->createOval(0,0, 50, 50);  # creates a circle
$id = $canvas->createOval(0,0, 50, 100); # creates an oval
The options for the oval will be familiar, so we'll just cover them briefly:
-fill => color
The oval will be filled in with the specified color. This color is different than the outline color. By default, the oval is not filled.
-outline => color
The outline is the line drawn around the outside of the circle. Normally the outline is black, but it can be changed by using the
-outline option. If you make the outline and the fill color the same, the oval appears solid.
-stipple => bitmap
To fill the oval with a bitmap pattern (1 values in bitmap are colored, 0 values are transparent), use the
-stipple option. If the -fill option isn't used, -stipple has no effect. -stipple takes a default bitmap name or a file with a bitmap in it.
-tags => taglist
When you create an oval, use the
-tags option to assign tag names to them. The value associated with -tags is an anonymous list of tag names; for example:
$canvas->createOval(0,0, 100,100, -tags => ["oval", "blue"]);
You don't need to use a list if you are only specifying one tag name:
$canvas->createOval(0,0, 100, 100, -tags => "oval");
-width => amount
The
-width option changes how wide the outline of the oval is drawn. The default for -width is 1 pixel.
The Polygon Item
A polygon is merely a bunch of lines where the first point is connected to the last point automatically to create an enclosed area. The createPolygon method requires at least three x,y coordinate pairs. For instance, the following piece of code will create a three-sided polygon:
$id = $canvas->createPolygon(1000,1000, 850,950, 30,40);
Additional x,y coordinate pairs can be specified as well; for example:
$id = $canvas->createPolygon(1000,1000, 850,950, 30, 40, 500, 500);
The options you can specify with createPolygon are the same as those you use with createLine: -fill, -outline, -smooth, -splinesteps, -stipple, -tags, and -width. Just remember that createPolygon connects the first point to the last point to enclose the area.
The Rectangle Item
As if being able to create a rectangle using createLine or createPolygon weren't enough, we also have the createRectangle method. It only takes two x y coordinate sets, which are the opposite corners of the rectangular area:
$id = $canvas->createRectangle(10, 10, 50, 150);
Again, we have seen the options available for createRectangle with the other create methods: -fill, -outline, -stipple, -tags, and -width. Although I've covered these options already, here are a few examples:
# A blue rectangle with black outline:
$canvas->createRectangle(10,10, 50, 150, -fill => 'blue');
# A blue rectangle with a thicker outline:
$canvas->createRectangle(10,10, 50, 150, -fill => 'blue', -width => 10);
The Text Item
Finally, an item type that doesn't have lines in it! You can add text to a canvas widget by using the createText method. It requires an x,y coordinate pair, which determines where you place the text in the canvas, and the text to be displayed:
$id = $canvas->createText(0,0, -text => "origin");
The -text option is actually optional, but then you wouldn't see any text on the screen. Because there is no point in that, we will assume that you will always specify -text with a text value to display. The other options available for text items are as follows:
-anchor => "center" | "n" | "e" | "s" | "w" | "ne" | "nw" | "se" | "sw"
The -anchor option determines where the text is placed in relation to the x,y coordinate. The default is centered: The text will be centered over that point no matter how large the piece of text is.
-fill => color
The text is normally drawn in black; you can change this by using the
-fill option. The name of this option doesn't make much sense when you think about it in terms of text (normally our widgets use -foreground to change the color of the text). For example, -fill => 'blue' will draw blue text.
-front => fontname
You can change the font for the displayed text by using the
-font option.
-justify => "left" | "right" | "center"
If the displayed text has more than one line, the -justify option will cause it to be justified as specified. The default justification is to the left.
-stipple => bitmap
This option is a bit strange, but here it is anyway. If you specify a bitmap name (or file) with the
-stipple option, the text will be drawn by using the bitmap pattern. Most of the time, this will make the text unreadable, so don't use it unless you're using a large font.
-tags => taglist
The
taglist is a single tag name or an anonymous list of tag names to be assigned to this item.
-text => string
This option is not optional. The specified string is displayed in the canvas widget at the x,y coordinate.
-width => amount
This is another misnamed option because it does not change the width of each text character. It determines the maximum length of each line of text. If the text is longer than this length, the line will automatically wrap to a second line. The default value for amount is 0, which will only break lines at newline characters. Lines are always broken at spaces so words won't be cut in half.
Text item indexes
Methods that affect text items will sometimes ask for an index value. Text indexes for the regular text widget were covered in Chapter 8, and the index values for a canvas text item are similar. The only difference is that each item is considered only one line (even if it has "\n" characters in it). Index values are as follows:
n
A number value: for example, 0 or 12.0 is the first character, 1 is the second, and so on.
"end"
The character directly after the last one. Often used with the insert method to add to the end of the string.
"insert"
The character directly before the insertion cursor.
"sel.first"
The first character of the selected text. Only valid if there is a selection.
"sel.last"
The last character of the selected text. Only valid if there is a selection.
"@x,y"
The character closest to the point x,y of the canvas (not screen coordinates).
Deleting characters
To delete characters from within a text item, use the dchars method: $canvas-> dchars (tag/id, first [, last ]). Specify a tag or ID to match the text item(s) and the index at which to start deleting. If the end index isn't specified, all the characters to the end of the string will be deleted (including any "\n" characters).
Positioning the cursor
You can specifically place the blinking text cursor by using icursor: $canvas-> icursor (tag/id, index). The cursor will only show up immediately if the specified item has the current keyboard focus. You can still set the position of the cursor if it doesn't, it just won't display until the item does get the keyboard focus.
Index information
You can find out an index based on another index by using the index method. Don't get confused yet; here's an example:
$index = $canvas->index("textitem", "sel.first");
This will return the numerical index associated with the first selected character in the text item. If more than one item will match the tag or ID indicated (in this case it's a tag named "textitem"), then the first one found will be used.
Adding text
To add more text to a text item, use the insert method: $canvas->insert (tag/ id, index, string). The first argument is the tag or ID, which can match multiple items. The second argument is the index before which to insert the new string, and the last argument is the actual string to insert into the text item.
Selecting text
There are several methods you can use to programmatically select portions of the text. To clear the selection (any selection; there are no tags or IDs sent with this command), use $canvas->selectClear(). To select a portion of text, use selectFrom and selectTo. The following two lines of code select the text from beginning to end for the first item that matches the tag "texttag"
$canvas->selectFrom("texttag", 0);
$canvas->selectTo("texttag", "end");
You can add to the selection by using selectAdjust: $canvas->selectAdjust ("adjust", tag/id, index). You can get the ID of the item that currently has the selection in it by using $id = $canvas->selectItem().
The Widget Item
You can put any type of widget inside a canvas-buttons, checkbuttons, text widgets, or even another canvas widget (if you are a little crazy)-by using the createWindow method. Before calling createWindow, you must create the widget to put into the canvas. Here's an example:
$bttn = $canvas->Button(-text => "Button",
                        -command => sub {print "Button in canvas\n";});
$id = $canvas->createWindow(0,0, -window => $bttn);
There are a few things you should note about this example (which is fairly typical except the subroutine associated with the button doesn't do anything useful):
• The button is a child of the canvas widget. The button could be a child of an ancestor of the canvas (the button could be a child of the main window if the canvas is also a child of the main window). However, the button should not be a child of a different toplevel widget that has nothing to with the canvas.
• The createWindow method doesn't actually create the widget; it just puts it in the canvas. The button is placed at the specified coordinates inside the canvas and has not been placed on the screen with pack(), grid(), or place().
• The widget must be created before you call createWindow.
• You can click the button and the callback associated with it will be invoked, just as with any other button.
• When you create the widget, you can use any of that widget's options to configure it. You can continue to configure the widget by using the reference to it (e.g., $bttn).
The following options which you can use when you call createWindow are more like options you use with pack() than widget options:
-anchor => "center""n"  |  "e"  |  "s"  |  "w"  |  "ne"  |  "nw"  | "se"  |  "sw"
The widget will be placed at the x,y coordinates according to the -anchor value. The default is "center", which means that the widget will have its center point placed on x,y.
-height => amount
The widget will be given this height. If you don't use
-height, the widget will have the height it was created with (usually the natural size of the widget).
-tags => taglist
The taglist associates a tag with the widget. You can specify either a single tag string, or an anonymous list of tag names.
-width => amount
The widget will be given this width. If you don't use the
-width option, the widget will have the width it was created with (the natural size of the widget).
-window => $widget
This is a nonoptional option. If you don't specify -window, there will be no widget put in the canvas. The $widget is a reference to a widget item. You can create the widget beforehand or inline as follows:
$canvas->createWindow(0,0, -window => $canvas->Button(-text => "Button",
         -command => sub { print "Button!\";  }));
It makes sense to create the widget inline if you don't need to do anything fancy with it.
Configuring the Canvas Widget
As usual, to configure or get information about the canvas widget, you can use the configure and cget methods, explained in detail in Appendix A, Configuring Widgets with configure and cget. Remember, configure and cget operate on the entire canvas widget (possibly affecting the items within it).
Configuring Items in the Canvas Widget
To change the configuration options of any of the items within the canvas, you only need to know the tag name or the ID for that item. You can then use the itemcget and itemconfigure methods. They behave just like the cget and configure methods, except as a first argument, they take the tag or ID of the item(s). I use the term "item(s)" because a tag can refer to more than one item. Here are some examples:
$color = $canvas->itemcget("circle", -fill)
$canvas->itemconfigure($id_number, -fill => "yellow", -outline => 5);
Make sure the options you use with itemconfigure and itemcget are valid. Each item type has a list of valid options; they are listed earlier in this chapter with each create method.
When you set the -tags option, the itemconfigure method will replace any currently set tags for the item. The taglist associated with -tags can also be empty, which will essentially remove all tags.
Tags
Each item can also have a tag (or more than one tag) associated with it. We have seen tags used before in the text widget, where sections of text could be assigned a tag. A tag can be assigned when the item is created, or you can use the addtag method to assign a tag after the item has been created.
There are two special tags that are automatically assigned and maintained: the "current" and "all" tag refers to all the items in the canvas. The "current" tag refers to the topmost item that the mouse cursor is over. If the mouse cursor is outside of the canvas widget or not over an item, then the "current" tag does not exist.
You can use tags to make changes to many different items at once. For instance, if you want all circles to have the same color, but you want to be able to change it from time to time, then give all circles a "circle" tag when you create them. Using the itemconfigure method to change the configuration options of the items with the "circle" tag.
The following are some sample syntax lines for creating tags.
$canvas->addtag ("newtag", "above", tag/id);
The
"newtag" tag is added to the item that is above the tag/ID item. If there is more than one match for tag/ID, the last item found will be used so the "newtag" is directly above the tag/ID item in the display list. The display list
is created as you add items to the canvas and can be manipulated with the raise and lower methods.
$canvas->addtag("newtag", "all");
The keyword "all" is a special tag that includes every item currently in the canvas. Items added to the canvas after the call to addtag will not contain "newtag" in their taglist.
$canvas->addtag("newtag", "below", tag/id);
The "newtag" tag is added to the item that is directly below the tag/ID item. If more than one item matches the below tag/ID search, the lowest item in the list will be used.
$canvas->addtag ("newtag", "closest", x,y);
Use the "closest" tag to select the item closest to the x,y coordinates (in canvas coordinates). If more than one item matches, the last one found is used.
There are two more possible arguments for this form of addtag. You can specify a number that indicates how far out from the x,y coordinates items are to be considered. For instance, if you want an item that is within 10 pixels to be considered "closest", make the call as follows:
$canvas->addtag("newtag", "closest", 50, 100, 10);
You can also specify a starting tag/ID to start a search. The call would then look like this:
$canvas->addtag("newtag", "closest", x, y, 10, $tag_or_id);
By using this form, you can loop through all the closest items.
$canvas->addtag("newtag", "enclosed", x1, y1, x2, y2);
You can assign the same tag to several items within the area bounded by (x1,y1) to (x2,y2) by using the "enclosed" form of addtag. Items will only be given "newtag" if they are completely within the area. The coordinates must make sense when you specify them: x1 < x2 and y1 < y2.
$canvas->addtag("newtag", "overlapping",x1, y1, x2, y2);
To assign tags to any item that has any part inside a bounded region, use "overlapping" instead of "enclosed". Even if the item has only one pixel inside this area, it will still count. All other rules for the bounding area are the same as for "enclosed"
$canvas->addtag("newtag", "withtag", tag/id);
Assigns "newtag" to all the items with the tag or ID specified.
Binding Items Using Tags
Each item in a canvas can have an event sequence bound to it so that a callback will be invoked when that event sequence happens. This is similar to adding an event sequence binding for widgets except item tags or item IDs are used. (Remember, if you want to add a normal binding to the canvas widget itself, you must use Tk::bind (or canvasBind for Tk8.0 users) instead of just bind.)
The general form of bind is as follows:
$canvas->Tk::bind(tag/id [ , sequence, command]);
The sequence would be similar to "<Button-1>" or "<Double-1>". A complete definition and explanation of event sequence is available in Chapter 14, Binding Events.
When you create item bindings, keep in mind that only mouse and keyboard bindings are valid for items. You can't do any of the weird esoteric bindings that are available for all widgets.
Here is an example that changes the color of any items tagged with "blue" when the mouse is over it:
# When the mouse is over the item, color it blue
$c->Tk::bind("blue", "<Enter>",
          sub { $c->itemconfigure("blue", -fill => "blue"); });
# When the mouse is over the item, color it black.
$c->Tk::bind("blue", "<Leave>",
          sub { $c->itemconfigure("blue", -fill => "blue"); });
Finding Tags
You can use the find command to determine which items have a certain tag. The possible ways to call find are the same as those of addtag (except for the newtag argument). Here are the basic formats (see "Tags" earlier in this chapter for more details on what they mean and how they work):
$canvas->find("above", tag/id);
$canvas->find("all");
$canvas->find("below", tag/id);
$canvas->find("closest", x, y [ , additional_area ]  [ , tag/id ]);
$canvas->find("enclosed", x1, y1, x2, y2);
$canvas->find("overlapping", x1, y1, x2, y2);
$canvas->find("withtag", tag/id);
Getting Tags from a Specific Item
To get a list of all the tags associated with an item, use:
@list = $canvas->gettags(tag/id);
If the tag/ID matches more than one item, then the first item found is used. If the tag/ID doesn't match anything, an empty string is returned.
Retrieving Bounding Box Coordinates
When we talked about the scrolling region of a canvas, we saw an example of the bbox method. The bbox method returns a list with four elements that define the area in which all the specified tags exist. The example used the special "all" tag, which refers to every item in the canvas. This was how we used it to define our scrolling region. You can specify more than one tag/ID to search for as follows:
($1, $r, $t, $b) = $canvas->bbox("blue", "red");
Assuming that you have been assigning the tags "blue" and "red" to appropriately color items, this code would return the region in the canvas that encloses all blue and red items.
Translating Coordinates
When you set up a callback and use the Ev('x') and/or Ev('y') arguments to find out where the user clicked, you must translate that information into canvas coordinates (Ev is explained in Chapter 14). To do this, use the canvasx and canvasy methods:
$x = $canvas->canvasx(screenx [, gridspacing ]);
$y = $canvas->canvasy(screeny [, gridspacing ]);
Each method takes an optional gridspacing argument; then the canvas coordinate value will be rounded to the nearest value to fit the grid.
Moving Items Around
Once an item has been created on the canvas, you can move it by using one of two methods: move or coords. The move method takes a tag or ID to indicate which items to move and the amounts to add to the x and y coordinates:
$canvas->move(tag/id, xdistance, ydistance);
For instance, the following code will move items with the "blue" tag 100 pixels in the x direction and 100 pixels in the y direction:
$canvas->move("blue", 100, 100);
To move an item in the negative direction, simply specify a negative value for the xdistance and/or ydistance. The other method, coords, allows you to explicitly specify a new x and y location for the first item found that is identified by the tag or ID:
$canvas->coords(tag/id, newx, newy);
If the item requires more than one set of x, y coordinates, you simply continue to specify them:
$canvas->coords(tag/id, newx1, newy1, newx2, newy2...);
You can also find out where an item currently is in the canvas by using coords and not specifying the x or y coordinates:
@coords_list = $canvas->coords(tag/id);
Remember, the coords method only applies to the first item it finds that matches the given tag or ID.
Changing the Display List
Every time a method looks through all the items in the canvas for a specific tag or ID, it looks through the display list. The display list is created as items are added to the canvas. The first item added to the canvas is the first items in the display list, and items are added in order as they are created. Also, items created later are drawn above the ones created earlier if they overlap at all. To change the display order, use the raise and lower methods:
$canvas->raise(tag/id, abovetag/id);
$canvas->lower(tag/id, belowtag/id);
The first argument for each method is the tag or ID of the item(s) you want to move in the display list. The second is the tag or ID next to which the first item should be placed (either above or below). If the first tag or ID matches more than one item, they are all moved.
Note that if you use the Scrolled method to create the canvas, you can't use the item returned by that method to invoke either raise or lower; you'll get a nasty error about the wrong argument types because Scrolled is not invoking this version of raise or lower, but another one. Use the subwidget to get the actual canvas reference and the call to raise and lower will work.
Deleting Items
To remove an item (or more than one item) from the canvas completely, use the delete method. It takes a list of tag or IDs to remove from the canvas. It will delete all matches it finds for the tag names, so be careful that you aren't deleting something you don't want to delete. Here is an example that uses three separate tag/IDs:
$canvas->delete("blue", "circle", $id_num);
You can specify only one tag/ID or as many as you want.
Deleting Tags
You can remove tags from items by using the dtag method. There are two forms:
$canvas->dtag(tag);
$canvas->dtag(tag/id, deltag);
The first one will search for items with the specified tag and then delete the tag. The second will search for items that match the tag or ID and then delete the deltag (if it exists) from that item. This allows you to delete a subset of the tabs, rather than every single tag.
Determining Item Type
To determine an item's type, call the type method:
$canvas->type(tag/id);
If the tag or ID mathods more than one item, only the type of the first item is returned. The returned value will be a string describing the item type: "oval", "text", "rectangle", and so on.
Set Keyboard Focus
To assign the keyboard focus to an item, use the focus method:
$canvas->focus(tag/id);
If the item doesn't know what to do with the keyboard focus, nothing will happen. You'll use this to change the focus to widget within the canvas.
Rendering the Canvas as PostScript
You can get a copy of the canvas as postscript by using the postscript method. It will either return the PostScript output or, if the -file option is specified, put it in a file
$postscript = $canvas->postscript();
$canvas->postscript(-file=> "ps.out");
The following options allow you to control the output of the PostScript.
-colormap => \@array
Specifies that each element in @array must be a valid postscript command for setting color values; e.g.,
"1.0 1.0 0.0 setrgbcolor".
-colormode => "color" | "gray" | "mono"
Creates the postscript in full color, grayscale ("gray"), or black and white ("mono").
-file => filename
Specifies the file in which to put the PostScript output.
-fontmap => \@array
Each element in @array is a two-element array that contains a fontname and a point size. The fontname should be a complete font name so Tk will parse it correctly (e.g.,
"-*-Helvetica-Bold-O-Normal--*-140-*").
-height => size
Sets the height of the area to print. The default height is the canvas height.
-pageanchor => "n" | "e" | "s" | "W" | "center"
Indicates where the page should be placed over the positioning point specified by -pagex and -pagey options. Default is "center".
-pageheight => height
Sets the height of the printed page. The canvas image will be scaled to fit. height is any valid screen distance.
-pagewidth => width
Sets the width of the printed page. The canvas image will be scaled to fit.
-pagex => x
Sets the coordinate for the x positioning point. Can be any valid screen distance.
-pagey => y
Sets the coordinate for the y positioning point. Can be any valid screen distance.
-rotate => 0 | 1
If 1, the page is rotated into a landscape orientation. Default is portrait orientation.
-width => size
Sets the width of the canvas area to be printed. Defaults to the width of the canvas.
-x => x
Sets the left edge of the area to be printed (in canvas coordinates). Default is the left edge of the window.
-y => y
Sets the top edge of the area to be printed (in canvas coordinates). Default is the left edge of the window.
Scaling the Canvas
When you put a large number of items on the canvas, it's sometimes hard to see them all without scrolling all over the place. It's possible to scale the canvas, for
example, so it will shrink everything in half or explode it to twice the original size. The usage for scale is as follows:
$canvas->scale(tag/id, xorigin, yorigin, xscale, yscale);
The scaling is centered around the xorigin and yorigin. I suggest using the real origin (0, 0) unless you can come up with a good reason not to. Both xscale and yscale are the scaling factors used on each coordinate in each item. Here are some examples:
$canvas->scale("all", 0, 0, 1, 1);   # no change!
$canvas->scale("all", 0, 0, .5, .5); # make all 1/2 size
$canvas->scale("all", 0, 0, 2, 2);   # double everything
$canvas->scale("all", 0, 0, 3, 3);   # triple everything!
It's a great idea to add a Zoom In and Zoom Out button that takes care of the scaling for you. Keep track of the scaling factor in a variable ($scale, for instance); set it to 1 to start with. Multiply it by .5 to zoom out and by 2 to zoom in. The last thing you'll need to do is make sure that, if you insert any new items into the canvas, you multiply those coordinates by the scale factor as well (otherwise they will look either too large or too small compared to the rest of the canvas items).
Scanning
Use the scan method to implement scanning of the canvas:
$canvas->scanMark(x, y);
$canvas->ScanDragto(x, y)
The first call, $canvas->scanMark(x, y), records the x and y coordinates and the current canvas view. The second call, $canvas->scanDragto(x, y), causes the view in the canvas to be adjusted by 10 times the difference between these coordinates and the previous ones sent with scanMark. This makes the canvas look as if it was moved at high speed.
Scrolling Methods
The canvas widget can be scrolled both horizontally and vertically. The methods xview and yview are used to communicate with the scrollbars. See Chapter 6 for more information on how these methods work.
A Drawing Program Example
The canvas widget is very versatile and can be useful for displaying different types of items. One of the first things that comes to mind when people think of a canvas is a drawing program. To save you the trouble, I've written a rudimentary
drawing program called Quick Draw you can use to draw rectangles, ovals, and lines. You can also change the thickness of the objects before you draw them. It only requires a tiny bit of error-checking to make it a slicker program. Here's the code:
use Tk;
$mw = MainWindow->new;
$mw->title("Quick Draw");

$f = $mw->Frame(-relief => 'groove',
                -bd => 2,
                -label => "Draw:")->pack(-side => 'left', -fill => 'y');
$draw_item = "rectangle";
$f->Radiobution(-variable => \$draw_item,
                -text => "Rectangle"
                -value => "rectangle",
                -command => \&bind_start)->pack(-anchor => 'w');
$f->Radiobutton(-variable => \$draw_item,
               -text => "Oval"
               -value => "oval"
               -command => \&bind_start)->pack(-anchor=>'w');
$f->Radiobutton(-variable => \$draw_item,
                -text => "Line"
                -value => "line",
                -command => \&bind_start)->pack(-anchor => 'w');
$f->Label(-text => "Line Width:")->pack(-anchor => 'w');
$thickness = 1;
$f->Entry(-textvariable => /$thickness)->pack(-anchor => 'w');

$c = $mw->Scrolled("Canvas", -cursor => "crosshair")->pack(
               -side => "left", -fill => 'both', -expand => 1);
$canvas = $c->Subwidget("canvas");

&bind_start();

MainLoop;

sub bind_start {
  # If there is a "Motion" binding, we need to allow the user
  # to finish drawing the item before rebinding Button-1
  # this fcn gets called when the finish drawing the item again
  @bindings = $canvas->Tk: :bind("<Motion>");
  return if ($#bindings >= 0);

  if ($draw_item eq "rectangle" || $draw_item eq "oval" ||$draw_item eq "line") {
    $canvas->Tk: :bind("<Button-1>", [\&start_drawing, Ev('x'), Ev('y')]);
  }
}
 
sub start_drawing {
  my ($canv, $x, $y) = @_;
  $x = $canv->canvasx($x);
  $y = $canv->canvasy($y);

  # Do a little error checking
  $thickness = 1 if ($thickness !~ /[0-9]+/);
  if ($draw_item eq "rectangle") {

    $canvas->createRectangle($x, $y, $x, $y,
       -width => $thickness, -tags => "drawmenow");
  } elsif ($draw_item eq "oval") {
    $canvas->createOval($x, $y, $x, $y,
       -width => $thickness, -tags => "drawmenow");
  } elsif ($draw_item eq "line") {
    $canvas->createLine($x, $y, $x, $y,
       -width => $thickness, -tags => "drawmenow");
  }

  $startx = $x; $starty = $y;
  # Map the Button-1 binding to &end_drawing instead of start drawing
  $canvas->Tk::bind("<Motion>", [\&size_item, Ev('x'); Ev('y')]),
  $canvas->Tk::bind("<Button-1>", [\&end_drawing, Ev('x'), Ev('y')]);
}

sub size_item {
  my ($canv, $x, $y) = @_;
  $x = $canv->canvasx($x);
  $y = $canv->canvasy($y);

  $canvas->coords("drawmenow", $startx, $starty, $x, $y);
}

sub end_drawing {
  my ($canv, $x, $y) = @_;
  $x = $canv->canvasx($x);
  $y = $canv->canvasy($y);

  # finalize the size of the item, and remove the tag from the item
  $canvas->coords("drawmenow", $startx, $starty, $x, $y);
  $canvas->dtag("drawmenow");

  # remove motion binding.
  $canvas->Tk::bind("<Motion>", "");
  &bind_start();
}
Note that I didn't set the -scrollregion at all because I wanted to create a limitless drawing space for the user. (This was the easiest way to provide this functionality: Do nothing!) It's a cute little program that demonstrates how to use bind and a few of the canvas methods. Figure 9-4 shows a screen shot of the application after a few items have been drawn on it.
0209-01.gif
Figure 9-4.
Quick Draw application screen
Fun Things to Try
The Quick Draw application doesn't do much that is useful, but here are some ideas for features to add to the application:
• Add the capability to print to a PostScript file.
• Create a Save Drawing feature that will loop through all the items and write out their types and coordinates to a text file. Of course, you'll need a Load Drawing feature as well.
• Allow the user to create text items.
• Add an entry widget that lets you change the color (by typing in a colorname) with which to draw the items.