8—
The Text Widget
When you think about what a text widget might do, you automatically think, "it displays text." This is true, yet it can do quite a bit more. The text widget is one of the most powerful standard widgets available in Perl/Tk. It is flexible, configurable, and easy to use for simple tasks. Here are some examples of how you can use text widgets:
• Display and edit a plain text file.
• Display formatted text from an HTML document.
• Create a scrollable color key, with buttons that allow you change the colors
• Gather multiline, formatted text (including colors) from a user (mini word processor).
• Display text with different colors based on the input.
• Make certain portions of text "clickable" and perform an action when clicked on. This could be HTML, or it could be similar to the widget demo.*
You can put simple text, formatted text, and other widgets inside a text widget. A text widget can be used in conjunction with scrollbars to allow many pages of information to be viewed in much less space.
Creating and Using a Text Widget
To create a text widget, use the Text method from the desired parent widget:
$text = $parent->Text ( [ options ... ])->pack;
* When you installed the Tk module with Perl, you also installed the widget demo. Type widget on the command line to see the capabilities of widgets in Perl/Tk.
After the text widget is created, there are several different ways to place text in it. The user can type directly into it, or you can use the insert method:
$text->insert('end', "To be or not to be...\nThat is the question");
The basic form of the insert method takes two arguments. The first is an index value that indicates where to start placing the text. The second argument is the string to insert. Unlike the listbox insert method, You can't use an array as the second argument. If you do, only the first item in the array is inserted into the text box.
A typical use of the text widget is to read a file and place it in the text widget as it's read:
$text = $mw->Scrolled("Text")->pack();
open (FH, "chapter1") || die "Could not open chapter1";
while (<FH>) {
  $text->insert ('end', $_);
}
close(FH);
You can use the text widget to display the file backward (line by line) by changing the insert line to $text->insert (0, $_). This will put the next line read at the top of the text widget instead of at the end.
The text widget can do a lot more than just display a file or two lines from a Shakespearean play. In addition to options, we also have tags, indexes, and marks to control how the contents of a text widget are displayed.
Text Widget Options
Options used with the Text method change the way the text is displayed within the text widgets. The following options are standard for all the widgets: -background, -borderwidth, -cursor, -exportselection, -foreground, -highlightbackground, -highlightcolor, -highlightthickness, -insert-background, -insertborderwidth, -insertofftime, -insertontime, -insertwidth, -padx, -pady, -selectbackground, -selectborderwidth, -selectforeground, -setgrid, -state, -takefocus, -wrap, -xscrollcommand, and -yscrollcommand.
To find out more about what these options do, check back to Chapter 3, The Basic Button, where they were first covered.
-background => color
Changes the color of the screen displayed behind the text.
-borderwidth=> amount
Sets the width of the edges of the widget.
-cursor => cursorname
Sets the cursor displayed when the mouse cursor is in front of the text widget.
-exportselection => 0 | 1
Determines if the text selected within the widget can also be used by the windowing system (such as X windows).
-font => fontname
Sets the font in which the text is displayed.
-foreground => color
Sets the color of the text.
-height => amount
Sets the height of the widget. Default is 24.
-highlightbackground  => color
Sets the color the highlight rectangle around the widget should be when it does not have the keyboard focus.
-highlightcolor => color
Sets the color the highlight rectangle around the widget should be when it does have the keyboard focus.
-highlightthickness => amount
Sets the thickness of the highlight rectangle around the widget. Default is 2.
-insertbackground => color
Changes the color of the insert cursor.
-insertborderwidth => amount
Changes the width of the insert cursor.
-insertofftime => time
Sets the time the insert cursor blinks in the off position. Default is 300.
-insertontime => time
Sets the time the insert cursor blinks in the on position. Default is 600.
-insertwidth => amount
Sets the width of the insert cursor.
-padx => amount
Adds extra space to the left and right of the text inside the text widget's edge.
-pady => amount
Adds extra space to the top and bottom of the text inside the text widget's edge.
-relief => 'flat' | 'groove' | 'raised' | 'ridge' | 'sunken' | 'solid'
Sets the relief of the edges of the widget. Default is 'sunken'.
-selectbackground => color
Sets the color of the area behind the selected text.
-selectborderwidth => amount
Sets the width of the border of the selected area.
-selectforeground => color
Sets the color of the selected text.
-setgrid => 0 | 1
Enables gridding for the text widget. Default is 0.
-spacing1 => amount
Sets the amount of additional space left on top of a line of text that begins on its own line. Default is 0.
-spacing2 => amount
Sets the amount of additional space left on top of a line of text after it has been wrapped around automatically by the text widget. Default is 0.
-spacing3 => amount
Sets the amount of additional space left on top of a line of text after it has been wrapped around automatically by the text widget. Default is 0.
-state => 'normal' | 'disabled'
Indicates the state of the text widget. Default is 'normal'. If set to 'disabled', no text can be inserted by either the user or the application (via the insert method).
-tabs => list
Specifies a list of tab stops to use in the text widget. Default is undefined (or no tab stops).
-takefocus => 0 | 1 | undef
Determines if widget can obtain keyboard focus.
-width => amount
Sets the width of the text widget in characters. Default is 80.
-wrap => "none" | "char" | "word"
Sets the mode used to determine automatic line wrapping. Default is "char"
=xscrollcommand => callback
Determines the callback used when the text widget is scrolled horizontally.
-yscrollcommand => callback
Determines the callback used when the text widget is scrolling vertically.
Fonts
You can use the -font option to change the font, including how large or small the text is (see Figure 8-1). This defines the default font for the entire text widget. Text that is inserted without a text tag (a tag allows you specify special formatting that applies only to certain portions of the text) will use this font.
The use of fonts was covered in Chapter 3, the first time we saw the -font option.
0158-01.gif
Figure 8-1.
Text widget using -font => ''r16"
Widget Size
When you first create a text widget, it will usually have a height of 24 lines and a width of 80 characters. Depending on how you put the text widget in the window (whether you use pack with the -expand and -fill options or grid with -sticky => "nsew"), it can change size when the window changes size. To force a certain size, you can use the -width and -height options:
# Text widget 20 characters wide and 10 lines tall
$mw->Text(-width => 20, -height => 10)->pack;
The values associated with -width are in characters, and the values associated with -height are lines of text. It is possible that the text widget will not be that exact width and height if you force the window to be larger via the minsize routine (i.e., $mw->minsize(400,400)), especially if you used -expand => 1 and -fill => 'both' with the pack command. So if you don't see what you expect on the screen the first time out, keep this in mind.
Widget Style
As with other widgets, you can change how the edges of the text widget are drawn using -relief and -borderwidth options. The examples shown in Figure 8-2 might not look much like text widgets, but trust me-they are!
Line Spacing
When text is displayed in a text widget, it can wrap around automatically if the line becomes longer than the text widget can display. The amount of room left between lines is defined by using the -spacingN options. Figure 8-3 shows the different areas that -spacing1, -spacing2, and -spacing3 affect.
The -spacing1 option affects how much room is above a new line of text (the first line in a paragraph). The -spacing2 option affects the space between lines when text that is wrapped automatically is too long to fit on one line. The -spacing3 option determines how much room is left after a paragraph is finished (right after an explicit newline).
0159-01.gif
Figure 8-2.
Text widgets showing different -relief values (also shows use of -width and -height
options to force smaller size)
0159-02.gif
Figure 8-3.
Example of -spacingN options
Tab Stops
The default setup for text widget tab stops is every eight characters. Each tab equals eight spaces (but it doesn't actually use spaces). You can replace this default setting by using the -tabs option as follows:
-tabs => [qw/2 center/]   # Place tabs every 2 pixels
-tabs => [2, "center"]    # The same thing, different syntax
The argument that goes with -tabs is an anonymous list that specifies positions in which to place each of the tab stops. You can also specify an optional justification value for each tab stop (as in the preceding example) after each tab stop's
numerical value. This all sounds much more confusing than it really is. Here are some examples to help clarify things:
-tabs => [qw/1i center/]  #every inch, text centered on tab-stop
-tabs => [qw/1i 1.5i/]    # ts at 1 inch, 1.5 inch and every .5 inch after
The default justification is "left". The possible justification values are "left", "right", "center", or "numeric".
When you specify the values (whether in centimeters, inches, or pixels), they are not cumulative. The list ["1i", "1.5i"] translates to one tab stop at 1 inch from the left edge of the text widget, and the next tab stop will be 1.5 inches from the left edge. If the specified list isn't long enough to span the entire window, the distance between the last two tab stops specified will be repeated across the screen.
Of course, setting up new tab stops is pretty useless unless you're doing major text editing, so in most cases, you'll leave this option alone.
You can reset the tab stops back to the default by setting -tabs to undef:
$text->configure(-tabs => undef);
A Short Break for a Simple Example
Before we get into some of the more complex (and more fun) things you can do with a text widget, let's look at complete use of the text widget.
This is a short program that will display a file, let you make changes to it, and then save it:
use Tk;
$mw = MainWindow->new;
# Create necessary widgets
$f = $mw->Frame->pack(-side => 'top', -fill => 'x');
$f->Label(-text => "Filename:")->pack(-side => 'left', -anchor => 'w');
$f->Entry(-textvariable => \$filename)->pack(-side => 'left',
   -anchor => 'w', -fill => 'x', -expand => 1);
$f->Button(-text => "Exit", -command => sub { exit; } )->
  pack(-side => 'right');
$f->Button(-text => "Save", -command => \&save_file)->
  pack(-side => 'right', -anchor => 'e');
$f->Button(-text => "Load", -command => \&load_file)->
  pack(-side => 'right', -anchor => 'e');
$mw->Label(-textvariable => \$info, -relief => 'ridge')->
  pack(-side => 'bottom', -fill => 'x');
$t = $mw->Scrolled("Text")->pack(-side => 'bottom',
  -fill => 'both', -expand => 1);

MainLoop;

# load_file checks to see what the filename is and loads it if possible
sub load_file {
 
  $info = "Loading file '$filename' ...";
  $t->delete("1.0", "end");
  if (!open(FH, "$filename")) {
    $t->insert("end", "ERROR: Could not open $filename\n");
            return;
  }
  while (<FH>) { $t->insert("end", $_); }
  close (FH);
  $info = "File '$filename' loaded";
}

# save_file saves the file using the filename in the entry box.
sub save_file {
  $info = "Saving '$filename'";
  open (FH, ">$filename");
  print FH $t->get("1.0", "end");
  $info = "Saved.";
}
Figure 8-4* shows the window when a document has been loaded and saved.
0161-01.gif
Figure 8-4.
Simple file editor with a "textfile" loaded
Text Indexes
When we talked about listbox index values, each index referred to a line in the listbox. The first line in the listbox was at index 0, and so on. With a text widget, the index can point to a specific line, but it can also point to a character within that line. An index for a text widget is built by using a base index and then optionally modifying that index with a modifier. The entire index, base, and modifier should be put in double quotes.
* For those of you paying attention, you'll notice this screenshot looks slightly different. That's because this was taken off of Windows 95 instead of X Windows. Note the "Tk" in the upper left-hand corner, and the Windows controls in the upper-right.
Base Index Values
"n.m"
This format allows you to explicitly specify a line number and a character number within that line. Lines start at 1 (which is different than the listbox widget), and characters start at 0.
"@x,y"
The character in the widget that is closest to the x,y coordinate.
"end"
The very end of the text widget, after any "\n" characters as well.
"mark"
Specifies the character after the location named mark. The two mark names provided by Tk are
"current" and "insert". What they refer to is discussed later in this chapter.
"tag.first"
A tag name is simply a placeholder for some special formatting instructions (discussed in the very next section). After creating tags, you can use this index form. tag.
first is the first character in the text widget that is of type tag. That is, you could create a "heading" tag and use "heading.first" index.
"tag.last"
Specifies the character directly after the text marked with tag.
$widget
If you have an embedded widget, you can refer to its location within the text widget by the variable referring to it.
$image
You can have embedded images as of Tk8.0. You can refer to its location by using the variable referring to it.
Index Modifiers
The index modifiers can be used following a base index value.
[ + | - ] count [ chars | lines ]
You can use the
+ and - to add/subtract lines and characters to a base index. The index "end - 1 chars" refers to text on the line before the "end". Be careful when you use this, though, because any "\n" lines also count as a complete line.
linestart
Modifies the index to refer to the first character on that line; i.e., $t-> insert ("end linestart", $string) will insert the string at the front of the last line in the text widget. insert will place the new text before the index given.
lineend
Refers to the last character in the line (usually the newline). It is useful when you don't know the exact number of characters in a line but want to insert text at the end of it.
wordstart
Adjusts the index to refer to the first character at the start of the word that contains the base index.
wordend
Adjusts the index to refer to the character after the end of the word that contains the base index.
Text Index Examples
"end"
The position right after the last line of text in the widget, no matter how much text is in the widget.
"1.0"
The first character on the first line in the text widget. The 1 represents the line, and 0 represents the character.
"2.0 - 1 chars"
The last character on the end of the first line. We reference it by using the first character on the second line (2.0) and subtracting one character value from that. If we used the insert method with this item, we would insert the text right before the "\n" at the end of the first line.
"1.end"
Also the last character on the end of the first line. This is a simpler way of getting to it.
"2.0 lineend"
The end of the second line. It is necessary to specify 2.0, not just 2, because 2 is an invalid base index.
The basic indexes are easy. When you start doing index arithmetic, it becomes a little more complicated. You just have to remember that you are referring to a position in the text widget that may change if other text has been inserted or deleted (either by the user or the application).
Although some of the combinations may seem silly (for example, "1.0 line-start"), keep in mind that you will most likely be calling methods that return indeterminate information about an event. For example, a user clicks in the text widget and presses a button that will increase the font size of that entire line. The index arithmetic allows you to reference that entire line without even knowing for sure which line it is on.
Text Tags
Text tags give you another way to address portions of text in the text widget. A tag has three purposes, and the same tag can serve all three or only one:
• Assigning formatting information to a portion(s) of text
• Associating a binding with text in the widget
• Managing selected text
Tags are also used to change how the text appears on the screen: font, size, coloring, and spacing are among a few of the text properties affected by tags. You change text properties by creating your own tags (with their own names), and using option/value pairs to assign formatting information. In addition to changing the formatting, you can use a tag to apply a specific binding (such as perform a task when the user clicks on that text). A special tag "sel" manages the selected text. Anytime the user selects some text, the location of that text is marked with the tag "sel".
Any of the text within the text widget can have one or more tags associated with it. If you apply two tags to the same piece of text and they both alter the font, the last tag applied wins.
Options Used With Tags
The options you can use to configure tagged text are mostly a subset of the configuration options of the text widget itself. There are some options that can only be used through tagged text.
-background => color
Sets the color of the area behind the text.
-bgstipple => pattern
Sets the pattern used to draw the area behind the text. Can create a shaded look.
-borderwidth => amount
Sets the width of the relief drawn around the edges of the text, line by line.
-fgstipple => pattern
Sets the pattern used to draw the text.
-font => fontname
Sets the font used for the text.
-foreground => color
Sets the color of the text.
-justify => 'left' | 'right' | 'center'
Sets the position of the text within the text widget.
-lmargin1=> amount
Sets the amount of indentation from the left edge for the first line of a paragraph.
-lmargin2=> amount
Sets the amount of indentation from the left edge for the second and greater lines of a paragraph. Sometimes called a hanging indent.
-offset => amount
Sets the amount the text is raised or lowered from the baseline. Can be used to create superscripts and subscripts.
-overstrike => 0 | 1
If a true value, causes the text to have a line drawn through it.
-relief => 'flat' | 'groove' | 'raised' | 'ridge' | 'sunken'
Determines the way the edges of the text are drawn, line by line.
-rmargin => amount
Sets the amount of space left between the text and the right edge of the widget.
-spacing1 => amount
Sets the amount of additional space left on top of a line of text that begins on its own line. Default is 0.
-spacing2 => amount
Sets the amount of additional space left on top of a line of text after it has been wrapped around automatically by the text widget. Default is 0.
-spacing3 => amount
Sets the amount of additional space left after a line of text has been ended by a
"\n". Default is 0.
-tabs => list
Indicates the set of tab stops for this text. See "Tab Stops" earlier in this chapter for more detailed information.
-underline => boolean
Indicates that the text should be drawn with an underline.
-wrap => 'none' | 'char' | 'word'
Determines the mode in which the text is wrapped. 'none' means lines that are longer than the text widget is wide are not wrapped. 'char' will wrap at each character. 'word' will wrap between words.
A Simple Tag Example
Let's look at an example of how a simple tag is created and use it to insert some text into a text widget (the resulting screen is shown in Figure 8-5):
$t = $mw->Text()->pack();
$t->tagConfigure('bold', -font =>
                  "-*-Courier-Medium-B-Normal--*-120-*-*-*-*-*-*");
# Use -font => "{Courier New} 24 {bold}" for Win32 systems
$t->insert('end', "This is some normal text\n");
$t->insert('end', "This is some bold text\n", 'bold');
Line 1 creates the Text widget and places it on the screen.
Line 2 creates the 'bold' tag. Don't be fooled by the use of the word "configure" instead of "create." When you configure a tag, you are creating it. We created a tag named 'bold' and associated a different font with it (it happens to be the same as our Unix text widget default font, just the bold version).
At this point, we haven't changed anything in the text widget. We are just setting up to use the tag later in the code. You can use any name to indicate a tag as long as it is a valid text string. We could have named the tag "bold_font" or "big_bold_font" or ''tag1." If you have good programming style (and want to be able to maintain your code), use a name that indicates what the tag does.
Line 3 inserts some text into the text widget.
Line 4 inserts some more text into the text widget, but uses the 'bold' tag. The insert method allows us to specify a tag as the third argument. This causes that string of text to be inserted into the text widget and assigned the tag 'bold'. The 'bold' tag was configured to change the font, so any text with the 'bold' tag will be shown with the different font.
0166-01.gif
Figure 8-5.
Text widget with normal and bold text
This is a pretty simplified example. What if we want to alter text that has been typed in by the user? We can't use the insert method then. We use the tagAdd method:
$t->tagAdd('bold', '1.0', 'end');
This applies the 'bold' tag to all of the text within the text widget.
Using the "sel" Tag to Manipulate the Selection
The "sel" tag is a special tag that is maintained by the text widget. Any text that is selected by the user will be assigned the "sel" tag. You can also force the selection by using some of the tag methods (which we haven't covered yet) to put the "sel" tag on some text. For instance, to select the third line:
$t->tagAdd("sel", "3.0", "3.0 lineend");
Here's an example that shows how to add another tag to the currently selected text:
$t->tagAdd('bold', 'sel.first', 'sel.last') if ($t->tagRanges('sel'));
When you use the "sel" tag as part of an index, you need to make sure the tag, exists (using tagRanges) within the text widget first or you'll get a really nasty huge error.
Configuring and Creating Tags
The first thing you'll do with a tag is create it by using tagConfigure (unless you're using the automatically defined "sel" tag). The first argument to tagConfigure is the name of the tag. The rest of the arguments (which are optional) are option/value pairs as described in the earlier section, "Options Used with Tags." Here are some examples:
# creating a tag with no options
$text->tagConfigure("special");
# Creating a tag that will change the color
$text->tagConfigure("blue", -foreground => "blue");
# Creating a tag that will make underlined text
$text->tagConfigure("underline", -underline => 1);
# Creating a tag that changes the color and spacing
$text->tagConfigure("bigblue", -foreground => "blue",
                    -spacing2 => 6);
You can change the settings for an already created tag by using tagConfigure a second time. Any changes you make to the tag immediately affect any text on the screen that has that tag:
# Add background color to "blue" tag
$text->tagConfigure("blue", -background => "red");
# Change the spacing for "bigblue"
$text->tagConfigure("bigblue", -spacing2 => 12);
As with widget configure methods, you can use tagConfigure to find out the current settings for a specific tag. To get all the tag options and their values in a list of lists:
@listoflists = $text->tagConfigure("blue");
foreach $1 (@list) { print "@$l\n"; } # print it out
Each list within the list contains two elements: the option name and the value. You can also limit the information you retrieve to a single option:
($option, $value) = $text->tagConfigure("blue", -font);
If you only want information on the value for a particular option, use tagCget:
$value = $text->tagCget("bigblue", -spacing2)
Adding a Tag to Existing Text
We've already seen an example of using the tagAdd method. It allows you to add a tag to portions of text in the text widget. The usage of tagAdd is as follows:
$text->tagAdd('tagname', index1 [ , index2, index1, index2, ... ] )
You can add a tag to a single index or a range of indexes. This means you can add a tag to the text widget to multiple places at the same time. Let's say you wanted to add the tag 'heading' to the 1st, 12th, and 30th lines because they are the location of some heading information that you want to look different than the rest of the text. The tagAdd line would look like this:
$text->tagAdd('heading', '1.0', '1.0 lineend',
                         '12.0', '12.0 lineend',
                         '30.0', '30.0 lineend');
Now, assuming the formatting of 'heading' makes the font bigger, those lines now show up differently than the defaults from the rest of the text in the widget.
You can add more than one tag to a section of text. For example, you can have both a 'heading' tag and a 'color' tag. If both tags try to alter the same option (such as -font), the last setting for that option wins.
Once you place a tag on a range of text, any text inserted between the beginning and ending indices of that text will automatically get the tag of the characters surrounding it. This happens whether you are using insert without any specific tags or the user just types text into the text widget. If you specify a tag with insert, it overrides the surrounding tag.
Using Bind with Tags
One of the main reasons for tags is the ability to assign a binding to certain portions of the text. After creating a tag with tagConfigure, you can use bind so a callback will execute when a sequence of events happens (such as a mouse click) on that tagged text. On our button widgets, we have a default binding of <Button-1> that invoked the callback associated with the -command option. We can do the same thing with tagged text.
The best example is using text like a web hyperlink. When you click on the link, something happens: a new document is loaded or another window is created and presented to the user. The basic form of a tagBind call is as follows:
$text->tagBind(tagname [, sequence, callback ] )
The callback is similar to that specified for the -command callback on a button. The sequence is a description of the event that triggers the script. The only sequences you can specify are those that are keyboard or mouse related. (See Chapter 14, Binding Events, for more details on available events.)
The following code shows a psuedo-link example. All the link does when we click on it is show the end of the text widget:
$t = $mw->Scrolled("Text", -width => 40)->pack(-expand => 1,
                                               -fill => 'both');
$t->tagConfigure('goto_end', -underline => 1, -foreground => 'red');
$t->tagBind('goto_end', "<Button-1>", sub { shift->see('end'); } );

# Setup Bindings to change cursor when over that line
$t->tagBind('goto_end', "<Any-Enter>',
             sub { shift->configure(-cursor => 'hand2') });
$t->tagBind('goto_end', "<Any-Leave>",
             sub { shift->configure(-cursor => 'xterm') });
$t->insert('end', "END\n", "goto_end");

# Insert a bunch of lines
for ($i = 1; $i <= 100; $i++) {
  $t->insert('end', "$i\n");
}
Inside the subs in the tagBind calls, we use the shift command to invoke a method. We can do this because the first argument sent to the bind callback is the text widget. This is done implicitly for you. Whichever widget tagBind is invoked on is the widget that will be sent as the first argument to the callback. To use the text widget more than once in the callback, assign it to a local variable; for example, my $widget = shift.
If we created our text widget in the global scope of the program and placed a reference to the widget in the variable $t, we could also access the text widget in the callback via the $t variable. This is only possible because $t is in the global scope and available during the callback. If you have two different text widgets that you want to use the same callback with, use shift to get the correct text widget:
$t1->tagBind('goto_end', "<Button-1>", \&goto_end );
$t2->tagBind('goto_end', "<Button-1>", \&goto_end );
sub goto_end {
  my $text = shift;
  $text->see('end');
}
Using the same callback for both text widgets helps save space in your program.
To determine what the bindings are for a tagname, just use tagBind with only the tag name argument:
@bindings = $text->tagBind("tagname");
The list will be empty if there are no bindings currently for that tag.
Deleting All Instances of a Tag
Once a tag is created, you can use the tagDelete method to delete the tag:
$text->tagDelete(tagname [ , tagname ... ])
The tags are deleted completely when you use tagDelete. This means the text reverts back to the default configuration values, and any bindings or other information associated with those tags is also deleted.
The tagDelete method can be used if you are creating temporary tags dynamically within the program and you need to delete the tags later when the information is no longer valid.
Removing a Tag from the Text
To remove the tag from a specific block of text, you can use the tagRemove method:
$text->tagRemove(tagname, index1 [, index2, index1, index2 ...])
Specify the name of the tag and an index or range of indexes from which to remove the tag. This leaves the tag intact; it merely removes it from the specific text indicated with the indices.
Raising and Lowering Tags
When there are several tags applied to the same text, the last tag added to the text overrides the previous ones, and its configuration options are given priority. You can change the priority of the tags by using tagLower and tagRaise:
$text->tagLower(tagname [, belowtag ])
$text->tagRaise(tagname [ , abovetag ])
These methods take a tag name as the first argument. If there is no second tag argument, the first tag is given the highest or lowest priority. This affects the entire text in the text widget no matter where the tags are applied. If a second tag is specified, the first tag is specifically placed before or after the second tag.
Think of it as reordering a stack of tags (all applied to the same text). The tag on the top has the most say, and if it has a -foreground option of 'red', then all
the text with that tag will be red, regardless of what the other text tags set -fore-ground to. If we use tagRaise to move a tag with -foreground of 'blue' to the top, the tagged text will change to blue.
Getting Tag Names
You can find out all the different tags that apply to a specific index or to the whole text widget by using the tagNames method:
$text->tagNames([ index ])
If you specify an index, the list returned contains tags that only apply to that index. If a specific index isn't given, then the list returned contain all the tags that apply to the entire text widget whether or not that tag has been applied to text within the widget.
Determining Where a Tag Applies
If you know the name of the tag, you can find out where it applies in the text widget by using the range methods. The first method, tagRanges, returns a list that contains pairs of index values for the whole text widget:
@list = $text->tagRanges("tagname")
# returns ( begin1, end1, begin2, end2 ... )
If no text in the text widget has that tag, the returned list will be empty.
You can get the pairs of index values one at a time by using the tagNextrange method:
($start, $end) = $text->tagNextrange("tagname", index1 [ , index2 ])
The search for "tagname" will begin at index1 and go no farther than index2. If index2 is not specified, then the search will continue until the end of the text widget or until it finds the tagname, whichever comes first.
Inserting Text
Now that we've gone over text indexes and marks, we can talk in more detail about the methods for manipulating the widget's contents.
As we've seen from the many examples in this chapter, we use insert to put text into the text widget. The first argument is an index and indicates where the text will be inserted. The second argument is the string to insert. The next argument (which is optional) is a single tag name or a list of tag names to assign to the inserted text. The usage is:
$text->insert(index, string, [ taglist, string, taglist ...] )
So far we've only seen single tags used with insert. If you want to specify more than one tag, put the tag names into square brackets, creating a list:
$t->insert('end', "This is a very tagged line",
           [ 'tag1', 'tag2', 'tag3' ]);
To use different sets of tags, you can supply additional text lines and additional tag lists:
$t->insert('end', "This is the heading", ['heading', 'underline'],
                  "Second line", ['bold', 'blue']);
When you use the insert command to insert more than one set of text with different tags, make sure they always come in pairs: text, tags, text, tags, etc. If the tag used isn't defined (with tagConfigure), there will be no effect on the text but the tag will still be assigned to that text. You can create the tag later if you wish.
Deleting Text
To remove text from the text widget, you can use the delete method:
$text->delete(index1 [ , index2 ]);
The first index argument is required; the second is optional. If both are specified, then the first index must be less than or equal to the second. All the characters from index1 to (but not including) index2 are removed from the text widget. If you want to delete everything from the text widget, you can use $text-> delete ("1.0", 'end').
Retrieving Text
The get function is one you'll use a lot. It returns the text located from index1 to index2. If index2 isn't specified, just the character located at index1 is returned. The usage of get is as follows:
$t = $text->get(index1 [ , index2 ]);
As with any index ranges, index1 must be less than or equal to index2 or an empty string will be returned.
Translating Index Values
When you work with indexes, it is useful to be able to convert a complicated index form into a simpler one. The index method returns an index with the form line.char.
$newvalue = $text->index(index1);
The index1 value can be any valid index expression.
Comparing Index Values
You can compare two index values by using the compare method.
$text->compare(index1, op, index2);
You pass the first index, the test operation to perform, and the second index. The values for op are: "<", "<=", "==", ">=", and "!=". The function returns 1 if the test was true and 0 if it wasn't. The call
$status = $text->compare("1.0", "<=", "end");
returns a 1 because the index "1.0" is less than "end".
Showing an Index
By using the see method, you can cause the text widget to show the portion of it that contains index:
$text->see(index);
The text within the widget will be scrolled up or down as a result of this call. If the index is already visible, nothing happens.
Getting the Size of a Character
The bbox method returns a list containing four items that describe the box around the character at index:
($x, $y, $w, $h) = $text->bbox(index);
The first two items returned are the x and y coordinates of the upper-left corner. The last two are the width and height of the box. The bounding box only describes the visible portion of the character, so if it is half hidden or not visible at all, the values returned will reflect this.
Getting Line Information
The dlineinfo method returns a list of five items. These items describe the area of the line that contains index:
• X coordinate of the upper-left corner
• Y coordinate of the upper-left corner
• Width of the area
• Height of the area
• Baseline position of the line, measured from x
Here is an example call:
($x, $y, $w, $h, $base) = $text->lineinfo("index");
Unlike the bbox method, even areas not shown (due to nonwrapped characters) are used in the calculations as long as some of the line is showing. However, if the line is not visible at all on the screen, the list will be empty. If the line happens to wrap to multiple lines, the entire area is used.
Searching the Contents of a Text Widget
You can use the search method to search the text widget for a pattern or regular expression. The search method takes some optional switches, the pattern to search for, and an index at which to start searching:
$index = $text->search([switches], pattern, index, [ stopindex ])
If a match is made, the index returned will point to the first character in the match. If no match is made, an empty string is returned.
The possible switches are:
-forwards
Tells search to search forward through the text widget starting at index. This is the default.
-backwards
Tells search to search backward through the text widget starting at the character before index.
-exact
The pattern must match the text exactly. This is the default.
-regexp
The pattern will be considered as a regular expression.
-nocase
Ignores case between pattern and the text within the text widget.
-count => varname
varname
is a pointer to a variable (i.e.,
\$variable). The number of characters matched will be stored within that variable.
--
This option does nothing except force the next argument to be taken as the pattern even if the next string starts with a "-".
Here is a simple example of using search:
$result = $text->search(-backwards, "find me", 'end');

$location = $text->search(-nocase, "SWITCHES", "1.0");
Scrolling
The text widget can be scrolled both horizontally and vertically, so it implements both xview and yview methods. These two methods are described in Chapter 6, Scrollbars.
Marks
There are several ways to refer to different positions throughout the text widget. Index values refer to a character. Tags are named references to a specific character or characters. The term mark is used to refer to the spaces in between characters. Similar to tags, a mark has a name. For example, the "insert" mark refers to the position of the insert cursor. However, tags refer to the actual characters, and if those characters are deleted, the tag is no longer associated with those characters. The mark stays in place whether the characters surrounding it are deleted or other characters are added. Marks can only refer to one location within the text widget at a time.
Once it is created, you can use a mark as an index. The gravity of the mark will affect on which side the text will be inserted. If the gravity is 'right' (the default), the text will be inserted to the left of the mark because the mark is glued to the character to the right of the mark. If the gravity is 'left', the text will be inserted to the left of the mark and the mark will refer to the left of the last character inserted.
There are two special marks that are set automatically by the text widget: "insert" and "current". The "insert" mark is wherever the insert cursor is. The "current" mark is the position closest to the mouse and adjusts as the mouse moves (as long as a mouse button is pressed). Both marks are maintained internally and cannot be deleted.
You will also see a mark called "anchor" that shows up in the getNames method after you click in the text widget. It always has the same index value as the "insert" mark, but "anchor" might not always exist.
Setting and Getting the Gravity
To set the gravity of the mark, you can use markGravity:
$text->markGravity(markname [ , direction ])
The possible values for direction are "right" and "left". The default gravity for new marks is "right". If you don't specify a gravity, the current gravity for that mark is returned.
Determining Mark Names
To get a list of all the marks in the text widget, you can use markNames:
@names = $text->markNames ()
There are no arguments to the markNames function, and it returns a list. Here is an example of how to report the marks within the text widget:
$f->Button(-text => "Report",
           -command => sub { my @m = $t->markNames ();
                             foreach (@m) {
                               print "MARK: $_ at ", $t->index($_), "\n";
                           }})->pack(-side => 'left');
The results after clicking in the window to set the insertion cursor are as follows:
MARK: insert at 2.15
MARK: anchor at 2.15
MARK: current at 3.0
Creating and Deleting Marks
You can create a mark and set it at a specific index by using the markSet method.
$text->markSet(markname, index)
In addition to the markname you want to create, specify the index where the mark should be placed. For instance, if you always want to be able to insert at the end of line 3:
$text->markSet("end of line3", "3.0 lineend");
...
$text->insert("end of line3", "text to insert");
The markUnset method removes the mark from the text widget and deletes the mark completely. It will no longer show up in the markNames list after it has been unset, and it can't be used as an index value either. You can specify more than one markname in markUnset:
$text->markUnset (markname [ , markname, markname ... ])
Embedding Widgets
One of the best things you can do with a text widget is put other widgets (such as button or entry widgets) inside it. One advantage of embedding widgets is you can create a scrolled set of widgets on a line-by-line basis.
Before we go over all the different functions that are available to work with embedded widgets, let's look at a quick example. We often want to do a lot of data entry in a program, which means we need a lot of label and entry widgets.
Sometimes there are so many of them that it's hard to fit them all on the screen without making a mess of the window. By using a scrolled text widget and putting the label and entry widgets inside it, we can create a lot more widgets within a smaller space. Here's the code:
use Tk;
$mw = MainWindow->new;
$mw->title("Data Entry");
$f = $mw->Frame->pack(-side => 'bottom');
$f->Button(-text => "Exit",
            -command => sub { exit; })->pack(-side => 'left');
$f->Button(-text => "Save",
           -command => sub { # do something with %info;
                   })->pack(-side => 'bottom');
$t = $mw->Scrolled("Text", -width => 40,
                    -wrap =>  'none')->pack(-expand => 1, -fill => 'both');

foreach (qw/Name Address City State Zip Phone Occupation
             Company Business_Address Business_Phone/) {
         $w = $t->Label(-text => "$_:", -relief => 'groove', -width => 20);
         $t->windowCreate('end', -window => $w);
         $w = $t->Entry(-width => 20, -textvariable => \$info{$_});
         $t->windowCreate('end', -window => $w);
         $t->insert('end', "\n");
}
$t->configure(-state => 'disabled'); # disallows user typing

MainLoop;
Figure 8-6 shows the Win32 version of this window.
0177-01.gif
Figure 8-6.
Text widget containing other widgets
We disable the text widget before running Mainloop because we don't want the user to be able to type text directly into the text widget. This only disables the ability to enter or delete text-the internal widgets still function normally. We also turned off the -wrap option so the label and entry widgets don't accidentally drop down to the next line when the window is resized.
You could put a text widget inside another text widget, but you probably wouldn't want to.
The window Method
As you can see from the preceding example, we use the windowCreate method to insert an embedded widget. The widget should have already been created, and it should be a child of the text widget. The general syntax is:
$widget = $text->Widget( ... );
$text->windowCreate(index, -window => $widget, [option => value ] );
In our example above, we used the 'end' index. You can use any valid text widget index to insert the embedded widgets. The only option we used was a -window option with the reference to the new $widget.
Here are the available options for the window method:
-align => where
Possible values of
'baseline', 'bottom', 'center', or 'top'. It determines where the widget is placed within the line if it is not as tall as the line itself. The default is 'center'.
-padx => amount and -pady => amount
Add space around the widget in the x and y directions respectively (
-padx => 10).
-stretch => 0 | 1
Takes a boolean value (1 or 0). A true value will stretch the widgets to fill the line from top to bottom.
-window => $widget
Takes a reference to another widget.
There are several different forms of the window method. The first one, the "Create" form, creates the widget within the text widget. The "Names" form lets you know what types of widgets are embedded in the text widget:
@types = $text->windowNames();
The results look like this:
.text.radiobutton .text.label .text.button .text.entry .text.checkbutton
Use the windowCget function to get information about the options that were used when the window was created in the text widget:
$value = $text->windowCget(index, option;
In order to use windowCget you need to know the index the widget is currently occupying (each widget occupies one character in the text widget, even if it looks like it takes more space).
The ''Configure" form of window will allow you change the options associated with the widget at index or retrieve the value of the configuration option:
$text->windowConfigure(index [, option => value] );
Remember that the only options you can use with this method are -align, -padx, -pady, -stretch, and -window. Other than this, windowConfigure(...) behaves just like a regular widget's configure method. To make changes on the $widget directly, use $widget->configure(...).
Internal Debug Flag
The debug function takes an optional boolean argument:
$text->debug( [ boolean ] );
If the value passed in is true, then internal consistency checks will be turned on in the B-tree code associated with text widgets. If false, the checks will be left off. Without any argument, the debug method will return the value "on" if it has been turned on, and "off" if not. All text widgets in the application share the same debug flag.
Scanning
The scanMark and scanDragto methods are used internally within the text widget. A call to scanMark simply records the x, y passed in for use later with scanDragto. It returns an empty string:
$text->scanMark(x, y);
scanDragto also takes x, y coordinates, which are compared to the scanMark x, y coordinates. The view within the text widget is adjusted by 10 times the difference between the coordinates.
$text->scanDragto(x, y);
Fun Things to Try
• Create a scrollable text widget. Insert a button widget that has text describing the foreground color of the text widget and when you click the button, have it cycle between several different colors, updating the button's -foreground color and text. For a practical application, have several buttons, each associated with a different color in your application. When the user clicks the button, you can change the color to a different value (possibly using the ColorEdit composite widget).
• Create a text widget that will display a read-only file. Create two buttons on the window, one to decrease the font within the text widget, the other to increase it.,