[Previous] [Contents] [Next]

14. Text Widget

The Text widget allows multiple lines of text to be displayed and edited. It supports both multi-colored and multi-font text, allowing them to be mixed in any way we wish. It also has a wide set of key based text editing commands, which are compatible with Emacs.

The text widget supports full cut-and-paste facilities, including the use of double- and triple-click to select a word and a whole line, respectively.

Warning: GtkText is deprecated and unsupported. It is known to be buggy. It still works in FPC, but better use GtkTextView. Unfortunately, GtkTextView is not documented in the tutorial (yet?).


14.1 Creating and Configuring a Text Box

There is only one function for creating a new Text widget.

function gtk_text_new (hadj : PGtkAdjustment;
                       vadj : PGtkAdjustment) : PGtkWidget;

The arguments allow us to give the Text widget pointers to Adjustments that can be used to track the viewing position of the widget. Passing nil values to either or both of these arguments will cause the gtk_text_new() function to create its own.

procedure gtk_text_set_adjustments (text : PGtkText;
                                    hadj : PGtkAdjustment;
                                    vadj : PGtkAdjustment);

The above function allows the horizontal and vertical adjustments of a text widget to be changed at any time.

The text widget will not automatically create its own scrollbars when the amount of text to be displayed is too long for the display window. We therefore have to create and add them to the display layout ourselves.

   vscrollbar := gtk_vscrollbar_new(GTK_TEXT(text)^.vadj);
   gtk_box_pack_start(GTK_BOX(hbox), vscrollbar, false, false, 0);
   gtk_widget_show(vscrollbar);

The above code snippet creates a new vertical scrollbar, and attaches it to the vertical adjustment of the text widget, text. It then packs it into a box in the normal way.

Note, currently the Text widget does not support horizontal scrollbars.

There are two main ways in which a Text widget can be used:

In order for us to switch between these modes of operation, the text widget has the following procedure:

procedure gtk_text_set_editable (text : PGtkText; editable : gint);

The editable argument is a true or false value that specifies whether the user is permitted to edit the contents of the Text widget. When the text widget is editable, it will display a cursor at the current insertion point.

You are not, however, restricted to just using the text widget in these two modes. You can toggle the editable state of the text widget at any time, and can insert text at any time.

The text widget wraps lines of text that are too long to fit onto a single line of the display window. Its default behaviour is to break words across line breaks. This can be changed using the next procedure:

procedure gtk_text_set_word_wrap (text : PGtkText; word_wrap : gint);

Using this procedure allows us to specify that the text widget should wrap long lines on word boundaries. The word_wrap argument is a true or false value.


14.2 Text Manipulation

The current insertion point of a Text widget can be set using

procedure gtk_text_set_point (text : PGtkText; index : guint);

where index is the position to set the insertion point.

Analogous to this is the function for getting the current insertion point:

function gtk_text_get_point (text : PGtkText) : guint;

A function that is useful in combination with the above two functions is

function gtk_text_get_length (text : PGtkText) : guint;

which returns the current length of the Text widget. The length is the number of characters that are within the text block of the widget, including characters such as newline, which marks the end of lines.

In order to insert text at the current insertion point of a Text widget, the function gtk_text_insert() is used, which also allows us to specify background and foreground colors and a font for the text.

procedure gtk_text_insert (text : PGtkText;
                           font : PGdkFont;
                           foreground : PGdkColor;
                           background : PGdkColor;
                           chars : pchar;
                           length : gint);

Passing a value of nil in as the value for the foreground color, background color or font will result in the values set within the widget style to be used. Using a value of -1 for the length parameter will result in the whole of the text string given being inserted.

The text widget is one of the few within GTK that redraws itself dynamically, outside of the gtk_main() function. This means that all changes to the contents of the text widget take effect immediately. This may be undesirable when performing multiple changes to the text widget. In order to allow us to perform multiple updates to the text widget without it continuously redrawing, we can freeze the widget, which temporarily stops it from automatically redrawing itself every time it is changed. We can then thaw the widget after our updates are complete.

The following two procedures perform this freeze and thaw action:

procedure gtk_text_freeze (text : PGtkText);

procedure gtk_text_thaw (text : PGtkText);

Text is deleted from the text widget relative to the current insertion point by the following two functions. The return value is a true or false indicator of whether the operation was successful.

function gtk_text_backward_delete (text : PGtkText; 
                                   nchars : guint) : gint;

function gtk_text_forward_delete (text : PGtkText; 
                                  nchars : guint) : gint;

If you want to retrieve the contents of the text widget, then the macro GTK_TEXT_INDEX(t, index) allows you to retrieve the character at position index within the text widget t.

To retrieve larger blocks of text, we can use the function

function gtk_editable_get_chars (editable : PGtkEditable;
                                 start_pos : gint;
                                 end_pos : gint) : pgchar;

This is a function of the parent class of the text widget. A value of -1 as end_pos signifies the end of the text. The index of the text starts at 0.

The function allocates a new chunk of memory for the text block, so don't forget to free when you have finished with it.


14.3 Keyboard Shortcuts

The text widget has a number of pre-installed keyboard shortcuts for common editing, motion and selection functions. These are accessed using Control and Alt key combinations.

In addition to these, holding down the Control key whilst using cursor key movement will move the cursor by words rather than characters. Holding down Shift whilst using cursor movement will extend the selection.

Motion Shortcuts

Ctrl-A Beginning of line Ctrl-E End of line
Ctrl-N Next Line Ctrl-P Previous Line
Ctrl-B Backward one character Ctrl-F Forward one character
Alt-B Backward one word Alt-F Forward one word

Editing Shortcuts

Ctrl-H Delete Backward Character (Backspace) Ctrl-D Delete Forward Character (Delete)
Ctrl-W Delete Backward Word Alt-D Delete Forward Word
Ctrl-K Delete to end of line Ctrl-U Delete line

Selection Shortcuts

Ctrl-X Cut to clipboard Ctrl-C Copy to clipboard Ctrl-V Paste from clipboard


14.4 A GtkText Example

This is quite a long example, but hopefully this screen-shot should help you to figure out what each part is trying to do:

program TextExample;

uses gtk2, gdk2, glib2, sysutils, classes;

procedure text_toggle_editable (checkbutton : PGtkWidget;
                                text : PGtkWidget); cdecl;
begin
   gtk_text_set_editable(GTK_TEXT(text),
                         gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton)));
end;

procedure text_toggle_word_wrap (checkbutton : PGtkWidget;
                                 text : PGtkWidget); cdecl;
begin
   gtk_text_set_word_wrap(GTK_TEXT(text),
                          gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton)));
end;

procedure close_application (widget : PGtkWidget; data : gpointer); cdecl;
begin
   gtk_main_quit();
end;

var
   window, box1, box2, hbox, button, check, separator : PGtkWidget;
   table, vscrollbar, text			      : PGtkWidget;
   cmap						      : pGdkColormap;
   color					      : pGdkColor; //!!!
   fixed_font					      : pGdkFont;
   s						      : String;
   t						      : TStringList;
   i, j						      : Longint;
   s_as_char_ptr				      : pchar;
   curr_length					      : Integer;

begin
   gtk_init(@argc, @argv);
   
   window := gtk_window_new(GTK_WINDOW_TOPLEVEL);
   gtk_widget_set_size_request(GTK_WIDGET(window), 600, 500);
   gtk_window_set_resizable(GTK_WINDOW(window), true);
   g_signal_connect(window, 'destroy',
                    G_CALLBACK(@close_application), NIL);
   gtk_window_set_title(GTK_WINDOW(window), 'Text Widget Example');
   gtk_container_set_border_width(GTK_CONTAINER(window), 0);

   box1 := gtk_vbox_new(false, 0);
   gtk_container_add(GTK_CONTAINER(window), box1);
   gtk_widget_show(box1);

   box2 := gtk_vbox_new(false, 10);
   gtk_container_set_border_width(GTK_CONTAINER(box2), 10);
   gtk_box_pack_start(GTK_BOX(box1), box2, true, true, 0);
   gtk_widget_show(box2);

   table := gtk_table_new(2, 2, false);
   gtk_table_set_row_spacing(GTK_TABLE(table), 0, 2);
   gtk_table_set_col_spacing(GTK_TABLE(table), 0, 2);
   gtk_box_pack_start(GTK_BOX(box2), table, true, true, 0);
   gtk_widget_show(table);

   { Create the GtkText widget }
   text := gtk_text_new(nil, nil);
   gtk_text_set_editable(GTK_TEXT(text), true);
   gtk_table_attach(GTK_TABLE(table), text, 0, 1, 0, 1,
                    GTK_EXPAND or GTK_SHRINK or GTK_FILL,
		    GTK_EXPAND or GTK_SHRINK or GTK_FILL, 0, 0);
   gtk_widget_show(text);

   { Add a vertical scrollbar to the GtkText widget }
   vscrollbar := gtk_vscrollbar_new(GTK_TEXT(text)^.vadj);
   gtk_table_attach(GTK_TABLE(table), vscrollbar, 1, 2, 0, 1, GTK_FILL,
		    GTK_EXPAND or GTK_SHRINK or GTK_FILL, 0, 0);
   gtk_widget_show(vscrollbar);
   
   { Get the system color map and allocate the color red }
   cmap := gdk_colormap_get_system();
   color := new(pGdkColor);  //!!!???
   with color^ do
      begin
	 red := 65535; { 0xffff }
	 green := 0;
	 blue := 0;
      end;
   if gdk_colormap_alloc_color(cmap, color, false, true) then
      writeln('couldn''t allocate color');

   { Load a fixed font }
   fixed_font := gdk_font_load('-misc-fixed-medium-r-*-*-*-140-*-*-*-*-*-*');
   
   { Realizing a widget creates a window for it,
     ready for us to insert some text }
   gtk_widget_realize(GTK_WIDGET(text));
   
   { Freeze the text widget, ready for multiple updates }
   gtk_text_freeze(GTK_TEXT(text));

   { Insert some colored text }
   gtk_text_insert(GTK_TEXT(text), nil, @text^.style^.black, nil,
                   'Supports ', -1);
   gtk_text_insert(GTK_TEXT(text), nil, color, nil, 'colored ', -1);
   gtk_text_insert(GTK_TEXT(text), nil, @text^.style^.black, nil,
                   'text and different ', -1);
   gtk_text_insert(GTK_TEXT(text), fixed_font, @text^.style^.black, nil,
                   'fonts'+#10+#10, -1);

   { Load the file text.pas into the text window }
   t := tStringList.create;
   t.LoadFromFile('text.pas');
   j := t.Count - 1;
   for i := 0 to j do begin
      s := t.Strings[i] + #10;
      curr_length := length(s);
      s_as_char_ptr := StrAlloc(curr_length + 1);
      StrPCopy(s_as_char_ptr, s);
      gtk_text_insert(GTK_TEXT(text), fixed_font, nil, nil, s_as_char_ptr, curr_length);
   end;
   t.Clear;
   t.Free;

   { Thaw the text widget, allowing the updates to become visible }
   gtk_text_thaw(GTK_TEXT(text));

   hbox := gtk_hbutton_box_new();
   gtk_box_pack_start(GTK_BOX(box2), hbox, false, false, 0);
   gtk_widget_show(hbox);

   check := gtk_check_button_new_with_label('Editable');
   gtk_box_pack_start(GTK_BOX(hbox), check, false, false, 0);
   g_signal_connect(check, 'toggled',
                    G_CALLBACK(@text_toggle_editable), GTK_TEXT(text));
   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), true);
   gtk_widget_show(check);
   check := gtk_check_button_new_with_label('Wrap Words');
   gtk_box_pack_start(GTK_BOX(hbox), check, false, true, 0);
   g_signal_connect(check, 'toggled',
                    G_CALLBACK(@text_toggle_word_wrap), GTK_TEXT(text));
   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), false);
   gtk_widget_show(check);

   separator := gtk_hseparator_new();
   gtk_box_pack_start(GTK_BOX(box1), separator, false, true, 0);
   gtk_widget_show(separator);

   box2 := gtk_vbox_new(false, 10);
   gtk_container_set_border_width(GTK_CONTAINER(box2), 10);
   gtk_box_pack_start(GTK_BOX (box1), box2, false, true, 0);
   gtk_widget_show(box2);

   button := gtk_button_new_with_label('close');
   g_signal_connect(button, 'clicked',
                    G_CALLBACK(@close_application), nil);
   gtk_box_pack_start(GTK_BOX(box2), button, true, true, 0);
   GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
   gtk_widget_grab_default(button);
   gtk_widget_show(button);

   gtk_widget_show(window);

   gtk_main();
end.

[Previous] [Contents] [Next]