The Text Widget

The text widget can be used to display and edit arbitrary amounts of text, but it can also hold images and other widgets. It is a versatile widget with a large number of subcommands. We will not cover all functionality in this tutorial. Instead we limit ourselves to the basic functionality and some not so basic features.

Creating a text widget with a command like:

    grid [text .t]

brings up a widget that is actually a reasonably capable text editor in its own right. You can type in text, remove, copy and paste text as you go along in the usual ways.

Besides interactive use you can use subcommands like insert to add text programmatically:

    set infile [open "readme.txt"]
    set contents [read $infile]
    close $infile

    .t insert end $contents

Because the text that you want to display may not all fit on the screen, it is quite usual to add scrollbars to select what part is shown:

    scrollbar .h -orient horizontal -command {.t xview}
    scrollbar .v -orient vertical   -command {.t yview}
    text      .t -xscrollcommand {.h set} -yscrollcommand {.v set}

    grid .t .v -sticky news
    grid .h -  -sticky news

and we have a text widget and scrollbars that allow us to display and edit a large file.

One possible use of the text widget is displaying the output from an external program. In that case, you probably want to see the last lines of text by default as they are added to the widget. This can be achieved with the see command:

    .t see end

The chan event procedure that reads the output from the external process would look something like:

    proc displayOutput {channel} {

        if { [gets $channel line] >= 0} {

            .t insert end "$line\n" ;# Add a newline explicitly
            .t see end
        } else {
            ...
        }
    }

12.1  Positions in the text

In the above examples ‘end’ is a special position in the text that is contained within the text widget. You can use any position (or index as it is called in the manual pages) you like:

Of course you will want to get the text from the text widget, once you are done editing. This is easy:

    set contents [.t get 1.0 end]

will get you the complete contents of the text widget. You specify the beginning and end of the text you want to retrieve. So, if you are building a text editor, the procedure to save the contents would take a form like:

    proc saveText {filename} {

        set outfile [open $filename w]
        puts $outfile [.t get 1.0 end]
        close $outfile
    }

You can do calculations with indices:

This allows you to do some very sophisticated tasks in an easy way, like searching the text for particular words one by one (see below).

12.2  Tags

The text widget has a powerful mechanism for influencing the appearance of pieces of text: tags. Text can be given a particular colour or font via tags. Here is an example:

    .t tag configure header -font "Helvetica 16 bold"
    .t insert 1.0 "The header\n\n" header

By setting the font for the tag ‘header’ to a different font family and font size than the default, any text with that tag will stand out.

Tags can be used for more things than just the appearance:

Tags can be added like in the above example when you insert the text, but also later on:

    .t insert 1.0 "The header\n\n"
    .t tag configure header -font "Helvetica 16 bold"
    .t tag add header 1.0 1.end

A tag has effect on one or more ranges of text and there can be more than one tag that has such an effect:

    .t tag configure large -font "Helvetica 16"
    .t tag configure blue  -foreground blue

    .t insert end "This text has a blue colour" blue
    .t tag add large 1.0 1.9

Of course, conflicts may occur — one tag imposing a green colour, the other a blue colour, for instance. This is resolved by the priority of the tag - see the manual page on the tag raise and tag lower subcommands.

To find where the tags are in the text you can use the tag nextrange, tag prevrange and tag ranges subcommands. They return the indices of the first character that has the tag and the first character that has not.

Last but not least, tags exist independently in the text widget: their properties remain defined even if there is no text fragment that uses them. So you can prepare all the tags you need before filling the text widget and you do not have to worry that they disappear inadvertently.

12.3  Searching

Another useful feature of the text widget is that you can search for text. The search subcommand has a number of options, like: search for an exact string, ignore the case, use glob patterns or regular expression patterns. In general it returns the position index of the first character of the substring that fits the search pattern.

The following commands bring the first occurrence of the string ‘scroll’ into view:

    set index [.t search -exact "scroll" 1.0]
    .t see $index

You have to give the search subcommand a starting position, so in an editor application you can continue searching by increasing the index:

    set next [.t search -exact "scroll" $index+1c]

If you want to highlight the substring in green, use:

    .t tag configure found -foreground green
    .t tag add found $index $index+6c

or if you want to highlight the entire line:

    .t tag configure found -background green
    .t tag add found "$index linestart" "$index lineend"

Another nice feature is to highlight the word that contains the substring:

    .t tag configure found -background green
    .t tag add found "$index wordstart" "$index wordend"

That way computing the right positions is done entirely by the text widget itself.

12.4  Hypertext

As indicated before, you can define event bindings for tags. This means that you can enhance the text widget with hypertext behaviour. A short example of how to do that is given below:

    proc showMessage {} {
        .t tag configure hidden -elide 0
    }

    .t tag configure hidden    -elide 1
    .t tag configure hypertext -underline 1
    .t tag bind      hypertext <Button-1> {showMessage}

    .t insert end "Click "
    .t insert end "here" hypertext

    .t insert end "\nYoufound the hidden message!" hidden

This illustrates the principle: you can do virtually anything you want with this mechanism. It has in fact been used for a hypertext information system (see Wiki page http://wiki.tcl.tk/19649 for instance).

12.5  Formatted text

While it is easy to programmatically influence the appearance of the text or parts of the text, it is a bit trickier to make the text widget behave as a word processor would if you selected ‘bold text’. The widget must be instructed to add a tag to any text you type so that that text appears as bold.

One solution is to overload the bindings for key press events:

    bind .text <KeyPress> {+insertChar %K}

    proc insertChar {char} {

        if { [string length $char] == 1 } {
           .text insert insert $char $::texttag
           return -code break
        }
    }

This technique is used in the program below.

Tip: As the text widget relies on bindings for its proper behaviour, we only override the default behaviour for ‘ordinary’ keystrokes. All other events are passed on. Hence the ‘+’ in the bind command and the check on the length of the character.

The second problem that you need to solve is storing the text together with all the formatting. The text widget can be instructed to return its contents in detail. That is what the storeText procedure does in this program. From that information you can construct a storage format that preserves the formatting.
    proc mainWindow {} {

        global bold
        global italic
        global texttag

        #
        # Create the toolbar
        #
        set t [frame .toolbar]

        ttk::button      $t.store  -text Store -style Toolbutton \
                -command {storeText}
        ttk::checkbutton $t.bold   -text B     -style Toolbutton \
                -variable bold \
                -command {toggleFont}
        ttk::checkbutton $t.italic -text I     -style Toolbutton \
                -variable italic \
                -command {toggleFont}

        ttk::separator .sep

        text   .text

        grid   $t.store $t.bold $t.italic -padx 2 -sticky ns

        grid   $t    -sticky w
        grid   .sep  -sticky we
        grid   .text

        #
        # Tags for the various fonts
        #
        .text tag configure bolditalic -font "Times 12 bold italic"
        .text tag configure bold       -font "Times 12 bold"
        .text tag configure italic     -font "Times 12 italic"
        .text tag configure normal     -font "Times 12"

        .text tag add normal insert

        bind .text <KeyPress> {+insertChar %K}

        set texttag normal
        set italic  0
        set bold    0
    }

    # insertChar --
    #     Insert the typed character with the right tag
    #
    proc insertChar {char} {

        if { [string length $char] == 1 } {
            .text insert insert $char $::texttag
            return -code break
        }
    }

    # toggleFont --
    #     Make the text appear in bold or italic font or normal
    #
    # Side effects:
    #     Next letters typed in the text widget have the selected font
    #
    proc toggleFont {} {
        global bold
        global italic
        global texttag

        if { $italic } {
            if { $bold } {
                set texttag bolditalic
            } else {
                set texttag italic
            }
        } else {
            if { $bold } {
                set texttag bold
            } else {
                set texttag normal
            }
        }
    }

    # storeText --
    #     Dump the contents
    #
    # Side effects:
    #     Prints the contents of the text widget in a console
    #
    proc storeText {} {
        console show
        puts [.text dump 1.0 end]
    }

    # main --
    #     Start the program
    #
    #
    mainWindow

12.6  More information

The text widget has many more options and subcommands than can be described in a tutorial like this. To give an impression: