Modifying the Text in Code

While users can modify the text in the text widget interactively, your program can also make changes. Adding text is done with the insert method, which we used above to provide an initial value for the text widget.

Text Positions and Indices

When we specified a position of Index::line_char( 1, 0 ) (first line, first character), this was an example of an index. It tells the insert method where to put the new text (just before the first line, first character, i.e., at the very start of the widget). Indices can be specified in a variety of ways. We used another one with the get method: Index::end() means just past the end of the text. (Why "just past?" Text is inserted right before the given index, so inserting at End will add text to the end of the widget). Note that Tk will always add a newline at the very end of the text widget.

Here are a few additional examples of indices and how to interpret them:

attributedescription
Index::line(3)..The newline at the end of line 3
Index::line_char(1,0).chars(3)Three characters past the start of line 1
Index::line(2)..Index::end().chars(-1)The last character before the new line in line 2
Index::end().chars(-1)The newline that Tk always adds at the end of the text
Index::end().chars(-2)The actual last character of the text
Index::end().lines(-1)The start of the last actual line of text
Index::line_char(2,2).lines(2)The third character (index 2) of the fourth line of text
Index::line_char(2,5).linestart()The first character of line 2
Index::line_char(2,5).lineend()The position of the newline at the end of line 2
Index::line_char(2,5).wordstart()First char. of the word with the char. at index 2.5
Index::line_char(2,5).wordend()First char. after the word with the char. at index 2.5

Some additional things to keep in mind:

  • An index past the end of the text (e.g., Index::end().chars(100) is interpreted as Index::end().
  • Indices wrap to subsequent lines as needed; e.g., Index::line_char(1,0).chars(10) on a line with only five characters will refer to a position on the second line.
  • Line numbers in indices are interpreted as logical lines, i.e., each line ends only at the "\n." With long lines and wrapping enabled, one logical line may represent multiple display lines. If you'd like to move up or down a single line on the display, you can specify this as, e.g., Index::line_char(1,0).display_lines(2).

To determine the canonical position of an index, use the index( &self, index: Index ) method. Pass it any index expression, and it returns the corresponding index in the form "line.char". For example, to find the position of the last character (ignoring the automatic newline at the end), use:


#![allow(unused)]
fn main() {
txt.index( Index::end() )?;
}

You can compare two indices using the compare method, which lets you check for equality, whether one index is later in the text than the other, etc.


#![allow(unused)]
fn main() {
if txt.compare( index1, TkCmp::Equal, index2 )? {
    // same position
}
}

Deleting Text

While the insert method adds new text anywhere in the widget, the delete( &self, index: Index ) and delete_ranges( &self, ranges: Vec<Range<Index>> ) methods removes it. We can delete either a single character (specified by index) or a range of characters (specified by start and end indices). In the latter case, characters from (and including) the start index until just before the end index are deleted (the character at the end index is not deleted). So if we assume for each of these we start off with "abcd\nefgh" in the text widget:


#![allow(unused)]
fn main() {
txt.delete( Index::line_char(1,2) )?; // "abd\nefgh"
txt.delete_ranges( vec![ Index::line_char(1,1), Index::line_char(1,2) ])?; // "acd\nefgh"
txt.delete_ranges( vec![ Index::line_char(1,0), Index::line_char(2,0) ])?; // "efgh"
txt.delete_ranges( vec![ Index::line_char(1,2), Index::line_char(2,1) ])?; // "abfgh"
}

There is also a replace method that performs a delete followed by an insert at the same location.

Example: Logging Window

Here's a short example using a text widget as an 80x24 logging window for an application. Users don't edit the text widget at all. Instead, the program writes log messages to it. We'd like to display more than 24 lines (so no scrolling). If the log is full, old messages are removed from the top before new ones are added at the end.

// cargo run --example logging_window

use tk::*;
use tk::cmd::*;
use tk::text::*;

fn main() -> TkResult<()> {
    let tk = make_tk!()?;
    let root = tk.root();

    let log = root
        .add_text( -width(80) -height(24) -wrap("none") )?
        .grid(())?;

    let write_to_log = | msg: &str| -> TkResult<()> {
        let index = log.index( Index::end().lines(-1) )?;
        log.configure( -state("normal") )?;
        log.insert( Index::end(), msg )?;
        if log.index( Index::end().chars(-1) )? != Index::line_char(1,0) {
            log.insert( Index::end(), "\n" )?;
        }
        if let Index::LineChar( num_line, _, _ ) = index {
            if num_line > 24 {
                log.delete_ranges( vec![ Index::line_char(1,0)..Index::line_char(num_line-23,0) ])?;
            }
        }
        log.configure( -state("disabled") )?;
        Ok(())
    };

    for c in 'a'..='z' {
        write_to_log( &format!( "{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}", c ))?;
    }

    Ok( main_loop() )
}

Note that because the program placed the widget in a disabled state, we had to re-enable it to make any changes, even from our program.