@c -----------------------------------------------------------------------------
@c File     : gtk-text-view.texi
@c License  : GNU General Public License (GPL)
@c Language : English
@c Author   : Dieter Kaiser
@c Date     : 22.05.2012
@c Revision : 18.05.2014
@c
@c Copyright (C) 2012, 2013, 2014 by Dieter Kaiser
@c -----------------------------------------------------------------------------

@menu
* Text Widget Overview::
* Simple Multiline Text Widget::
* Formatted Text in Text Widget::
* Cut Copy and Paste::
* Searching::
* Examing and Modify Text::
* Images and Widgets::
* Buffer and Window Coordinates::
* Final Notes::
@end menu

@c -----------------------------------------------------------------------------
@node Text Widget Overview, Simple Multiline Text Widget, Top, Top
@section Text Widget Overview

@findex GtkTextBuffer
@findex gtk-text-buffer
@findex text-buffer, gtk-text-buffer

@findex GtkTextView
@findex gtk-text-view
@findex text-view, gtk-text-view

@findex GtkTextTag
@findex gtk-text-tag
@findex text-tag, gtk-text-tag

@findex GtkTextTagTable
@findex gtk-text-tag-table
@findex text-tag-table, gtk-text-tag-table

@findex GtkTextIter
@findex gtk-text-iter
@findex text-iter, gtk-text-iter

@findex GtkTexMark
@findex gtk-text-mark
@findex text-mark, gtk-text-mark

@findex gtk-text-buffer-place-cursor
@findex text-buffer-place-cursor, gtk-text-buffer-place-cursor

GTK+ has an extremely powerful framework for multiline text editing.  The
primary objects involved in the process are @xurl{gtk-text-buffer}, which
represents the text being edited, and @xurl{gtk-text-view}, a widget which can
display a @xurl{gtk-text-buffer}.  Each buffer can be displayed by any number of
views.

One of the important things to remember about text in GTK+ is that it's in the
UTF-8 encoding.  This means that one character can be encoded as multiple bytes.
Character counts are usually referred to as offsets, while byte counts are
called indexes.  If you confuse these two, things will work fine with ASCII, but
as soon as your buffer contains multibyte characters, bad things will happen.

Text in a buffer can be marked with tags.  A tag is an attribute that can be
applied to some range of text.  For example, a tag might be called "bold" and
make the text inside the tag bold.  However, the tag concept is more general
than that; tags don't have to affect appearance.  They can instead affect the
behavior of mouse and key presses, "lock" a range of text so the user can't edit
it, or countless other things.  A tag is represented by a @xurl{gtk-text-tag}
object.  One @xurl{gtk-text-tag} can be applied to any number of text ranges in
any number of buffers.

Each tag is stored in a @xurl{gtk-text-tag-table}.  A tag table defines a set of
tags that can be used together.  Each buffer has one tag table associated with
it; only tags from that tag table can be used with the buffer.  A single tag
table can be shared between multiple buffers, however.

Tags can have names, which is convenient sometimes (for example, you can name
your tag that makes things bold "bold"), but they can also be anonymous (which
is convenient if you're creating tags on-the-fly).

Most text manipulation is accomplished with iterators, represented by a
@xurl{gtk-text-iter}.  An iterator represents a position between two characters
in the text buffer.  @xurl{gtk-text-iter} is a struct designed to be allocated
on the stack; it is guaranteed to be copiable by value and never contain any
heap-allocated data.  Iterators are not valid indefinitely; whenever the buffer
is modified in a way that affects the number of characters in the buffer, all
outstanding iterators become invalid.  (Note that deleting 5 characters and then
reinserting 5 still invalidates iterators, though you end up with the same
number of characters you pass through a state with a different number).

Because of this, iterators can't be used to preserve positions across buffer
modifications.  To preserve a position, the @xurl{gtk-text-mark} object is
ideal.  You can think of a mark as an invisible cursor or insertion point; it
floats in the buffer, saving a position.  If the text surrounding the mark is
deleted, the mark remains in the position the text once occupied; if text is
inserted at the mark, the mark ends up either to the left or to the right of the
new text, depending on its gravity.  The standard text cursor in left-to-right
languages is a mark with right gravity, because it stays to the right of
inserted text.

Like tags, marks can be either named or anonymous.  There are two marks built-in
to @xurl{gtk-text-buffer}; these are named "insert" and "selection_bound" and
refer to the insertion point and the boundary of the selection which is not the
insertion point, respectively.  If no text is selected, these two marks will be
in the same position.  You can manipulate what is selected and where the cursor
appears by moving these marks around.  If you want to place the cursor in
response to a user action, be sure to use @xurl{gtk-text-buffer-place-cursor},
which moves both at once without causing a temporary selection (moving one then
the other temporarily selects the range in between the old and new positions).

Text buffers always contain at least one line, but may be empty (that is,
buffers can contain zero characters).  The last line in the text buffer never
ends in a line separator (such as newline); the other lines in the buffer always
end in a line separator.  Line separators count as characters when computing
character counts and character offsets.  Note that some Unicode line separators
are represented with multiple bytes in UTF-8, and the two-character sequence
"\r\n" is also considered a line separator.

@c -----------------------------------------------------------------------------
@node Simple Multiline Text Widget, Formatted Text in Text Widget, Text Widget Overview, Top
@section Simple Multiline Text Widget

@ifnotinfo
@float Figure, figure-simple-text-view
@caption{Most Simple Text View}
@center @image{figures/simple-text-view302x229, 201pt}
@end float
@end ifnotinfo

The simplest usage of @xurl{gtk-text-view} might look like in
@ref{example-simple-text-view}.  The output is shown in
@ref{figure-simple-text-view}.

@float Example, example-simple-text-view
@caption{Most Simple Text View}
@end float
@verbatim
(defun example-simple-text-view ()
  (within-main-loop
    (let* ((window (make-instance 'gtk-window
                                  :type :toplevel
                                  :title "Example Simple Text View"
                                  :default-width 300))
           (view (make-instance 'gtk-text-view))
           (buffer (gtk-text-view-buffer view)))
      (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (leave-gtk-main)))
      (gtk-text-buffer-set-text buffer "Hello, this is some text.")
      (gtk-container-add window view)
      (gtk-widget-show-all window))))
@end verbatim

@findex gtk-text-buffer-new
@findex text-buffer-new, gtk-text-buffer-new

@findex gtk-text-view-new-with-buffer
@findex text-view-new-with-buffer, gtk-text-view-new-with-buffer

@findex gtk-text-view-buffer
@findex text-view-buffer, gtk-text-view-buffer

In many cases it is also convenient to first create the buffer with
@xurl{gtk-text-buffer-new}, then create a widget for that buffer with
@xurl{gtk-text-view-new-with-buffer}.  Or you can change the buffer the widget
displays after the widget is created with @xurl{gtk-text-view-buffer}.

@c -----------------------------------------------------------------------------
@node Formatted Text in Text Widget, Cut Copy and Paste, Simple Multiline Text Widget, Top
@section Formatted Text in Text Widget

@ifnotinfo
@float Figure, figure-text-view-attributes
@caption{Changing Text Attributes of a Text View}
@center @image{figures/text-view-attributes352x229, 235pt}
@end float
@end ifnotinfo

@findex gtk-widget-override-font
@findex widget-override-font, gtk-widget-override-font

@findex gtk-text-view-set-tabs
@findex text-view-set-tabs, gtk-text-view-set-tabs

There are two ways to affect text attributes in @xurl{gtk-text-view}.  You can
change the default attributes for a given @xurl{gtk-text-view}, and you can
apply tags that change the attributes for a region of text.  For text features
that come from the theme - such as font and foreground color - use standard
@xurl{gtk-widget} functions such as @xurl{gtk-widget-override-font}.  For other
attributes there are dedicated methods on @xurl{gtk-text-view} such as
@xurl{gtk-text-view-set-tabs}.

@float Example, example-text-view-attributes
@caption{Changing Text Attributes of a Text View}
@end float
@verbatim
(defun example-text-view-attributes ()
  (within-main-loop
    (let* ((window (make-instance 'gtk-window
                                  :type :toplevel
                                  :title "Example Text View Attributes"
                                  :default-width 350))
           (view (make-instance 'gtk-text-view))
           (buffer (gtk-text-view-buffer view)))
      (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (leave-gtk-main)))
      (gtk-text-buffer-set-text buffer "Hello, this is some text.")
      ;; Change default font throughout the widget
      (gtk-widget-override-font
                             view
                             (pango-font-description-from-string "Serif 20"))
      ;; Change default color throughout the widget
      (gtk-widget-override-color view
                                 :normal
                                 (gdk-rgba-parse "red"))
      ;; Change left margin throughout the widget
      (gtk-text-view-set-left-margin view 30)
      ;; Use a tag to change the color for just one part of the widget
      (let ((tag (make-instance 'gtk-text-tag
                                :name "blue_foreground"
                                :foreground "blue"))
            (start (gtk-text-buffer-get-iter-at-offset buffer 7))
            (end (gtk-text-buffer-get-iter-at-offset buffer 12)))
        ;; Add the tag to the tag table of the buffer
        (gtk-text-tag-table-add (gtk-text-buffer-get-tag-table buffer) tag)
        ;; Apply the tag to a region of the text in the buffer
        (gtk-text-buffer-apply-tag buffer tag start end))
      ;; Add the view to the window and show all
      (gtk-container-add window view)
      (gtk-widget-show-all window))))
@end verbatim

@subheading More about Tags

Tag objects are associated with a buffer and are created using the function
@xurl{gtk-text-buffer-create-tag}.  Tags can be optionally associated with a
name @code{tag-name}.  Thus, the tag could be referred using the returned
pointer or using the @code{tag-name}.  For anonymous tags, @code{nil} is passed
to @code{tag-name}.  The group of properties represented by this tag is listed
as name/value pairs after the @code{tag-name}.  The list of property/value pairs
is terminated with a @code{NULL} pointer.  "style", "weight", "editable",
"justification" are some common property names.  The following table lists their
meaning and assignable values.

See the GTK+ manual @url{http://www.crategus.com/books/cl-cffi-gtk/}, for a
complete list of properties and their corresponding values.

The created tag can then be applied to a range of text using the functions
@xurl{gtk-text-buffer-apply-tag} and @xurl{gtk-text-buffer-apply-tag-by-name}.
The first function specifies the tag to be applied by a tag object and the
second function specifies the tag by it's name.  The range of text over with the
tag is to applies is specified by the start and end iters.  Below is an
extension of the previous example, that has a tool-bar to apply different tags
to selected regions of text.

@ifnotinfo
@float Figure, figure-text-tags
@caption{Searching text in a text view.}
@center @image{figures/text-view-tags, 201pt}
@end float
@end ifnotinfo

@float Example, example-text-tags
@caption{Applying tags.}
@end float
@verbatim
(defun on-button-clicked (buffer tag)
  (multiple-value-bind (start end)
      (gtk-text-buffer-get-selection-bounds buffer)
    (gtk-text-buffer-apply-tag-by-name buffer tag start end)))

(defun example-text-view-tags ()
  (within-main-loop
    (let* ((window (make-instance 'gtk-window
                                  :title "Multiline Text Input"
                                  :type :toplevel
                                  :default-width 300
                                  :default-height 200))
           (vbox (make-instance 'gtk-grid
                                :orientation :vertical))
           (bbox (make-instance 'gtk-grid
                                :orientation :horizontal))
           (text-view (make-instance 'gtk-text-view
                                     :hexpand t
                                     :vexpand t))
           (buffer (gtk-text-view-buffer text-view)))
      (g-signal-connect window "destroy"
                               (lambda (widget)
                                 (declare (ignore widget))
                                 (leave-gtk-main)))
      (gtk-container-add vbox bbox)
      (gtk-container-add vbox text-view)
      (gtk-text-buffer-set-text buffer "Hello World Text View")
      ;; Create tags associated with the buffer.
      (gtk-text-tag-table-add (gtk-text-buffer-get-tag-table buffer)
                              (make-instance 'gtk-text-tag
                                             :name "bold"
                                             :weight 700)) ; :bold
      (gtk-text-tag-table-add (gtk-text-buffer-get-tag-table buffer)
                              (make-instance 'gtk-text-tag
                                             :name "italic"
                                             :style :italic))
      (gtk-text-tag-table-add (gtk-text-buffer-get-tag-table buffer)
                              (make-instance 'gtk-text-tag
                                             :name "font"
                                             :font "fixed"))
      ;; Create button for bold.
      (let ((button (make-instance 'gtk-button :label "Bold")))
        (g-signal-connect button "clicked"
           (lambda (widget)
             (declare (ignore widget))
             (on-button-clicked buffer "bold")))
        (gtk-container-add bbox button))
      ;; Create button for italic.
      (let ((button (make-instance 'gtk-button :label "Italic")))
        (g-signal-connect button "clicked"
           (lambda (widget)
             (declare (ignore widget))
             (on-button-clicked buffer "italic")))
        (gtk-container-add bbox button))
      ;; Create button for fixed font.
      (let ((button (make-instance 'gtk-button :label "Font Fixed")))
        (g-signal-connect button "clicked"
           (lambda (widget)
             (declare (ignore widget))
             (on-button-clicked buffer "font")))
        (gtk-container-add bbox button))
      ;; Create the close button.
      (let ((button (make-instance 'gtk-button :label "Close")))
        (g-signal-connect button "clicked"
                          (lambda (widget)
                            (declare (ignore widget))
                            (gtk-widget-destroy window)))
        (gtk-container-add vbox button))
      (gtk-container-add window vbox)
      (gtk-widget-show-all window))))
@end verbatim

@subheading More Functions for Applying and Removing tags

@c TODO: gtk-text-buffer-insert-with-tags is not implemented

In the previous section, the function @xurl{gtk-text-buffer-insert} was
introduced.  A variant @code{gtk-text-buffer-insert-with-tags} of this function
can be used to insert text with tags applied.

The tags argument list is terminated by a @code{NULL} pointer. The
@code{_by_name} suffixed variants are also available, in which the tags to be
applied are specified by the tag names.  Tags applied to a range of text can be
removed by using the function @xurl{gtk-text-buffer-remove-tag}.

This function also has the @code{_by_name} prefixed variant.  All tags on a
range of text can be removed in one go using the function
@xurl{gtk-text-buffer-remove-all-tags}.

@subheading Formatting the Entire Widget

The above functions apply attributes to portions of text in a buffer.  If
attributes have to be applied for the entire @xurl{gtk-text-view} widget, the
@code{gtk-text-view-set-*} functions can be used.  For example, the function
@xurl{gtk-text-view-set-editable} makes @code{text-view} editable/non-editable.

See the GTK+ API documentation @url{http://www.crategus.com/books/cl-cffi-gtk/}
for a complete list of available functions.  The attributes set by these
functions, on the entire widget, can be overridden by applying tags to portions
of text in the buffer.

@c -----------------------------------------------------------------------------
@node Cut Copy and Paste, Searching, Formatted Text in Text Widget, Top
@section Cut, Copy and Paste

@c -----------------------------------------------------------------------------
@node Searching, Examing and Modify Text, Cut Copy and Paste, Top
@section Searching

@findex gtk-text-iter-forward-search
@findex text-iter-forward-search, gtk-text-iter-forward-search

@findex gtk-text-iter-backward-search
@findex text-iter-backward-search, gtk-text-iter-backward-search

@findex gtk-text-iter-search
@findex text-iter-search, gtk-text-iter-search

@findex gtk-text-buffer-get-selection-bounds
@findex text-buffer-get-selection-bounds, gtk-text-buffer-get-selection-bounds

@findex gtk-text-buffer-select-range
@findex text-buffer-select-range, gtk-text-buffer-select-range

The functions @xurl{gtk-text-iter-forward-search} and
@xurl{gtk-text-iter-backward-search} with the arguments @code{iter},
@code{str}, @code{flags}, @code{limit} can be used to search for a given text
within a buffer.  Both functions return as the first value a boolean to indicate
wether the search was sucessful.  If this is the case the second and third
values contain the iterators @code{match-start} and @code{match-end}.

The function @xurl{gtk-text-iter-forward-search} searches for @code{str}
starting from @code{iter} in the forward direction.  The start and end iterators
of the first matched string are return as the values @code{match-start} and
@code{match-end}.  The search is limited to the iterator @code{limit}, if
specified.  The function returns @code{nil}, if no match is found.  The
function @xurl{gtk-text-iter-backward-search} is same as
@xurl{gtk-text-iter-forward-search} but, as its name suggests, it searches in
the backward direction.

In the Lips binding to GTK+ we have in addition the function
@xurl{gtk-text-iter-search} which combines the functions for forward and
backward search and handles the arguments @code{flags} and @code{limit} as
keyword arguments.  In addition the keyword argument @code{direction} with a
default value of @code{:forward} indicates the direction of the search.  Set
the value of @code{direction} to @code{:backward} for backward search.

The function @xurl{gtk-text-buffer-get-selection-bounds} was introduced earlier,
to obtain the iterators around the current selection.  To set the current
selection programmatically the function @xurl{gtk-text-buffer-select-range} with
the arguments @code{buffer}, @code{start}, @code{end} can be used.  The function
sets the selection bounds of @code{buffer} to @code{start} and @code{end}.  The
following example which demonstrates searching, uses this function to highlight
matched text.

@ifnotinfo
@float Figure, figure-text-view-search
@caption{Searching text in a text view.}
@center @image{figures/text-view-search, 201pt}
@end float
@end ifnotinfo

@float Example, example-text-view-search
@caption{Searching text in a text view.}
@end float
@verbatim
(defvar *some-text*
        "One of the important things to remember about text in GTK+ is that
it is in the UTF-8 encoding. This means that one character can be encoded as
multiple bytes. Character counts are usually referred to as offsets, while
byte counts are called indexes. If you confuse these two, things will work
fine with ASCII, but as soon as your buffer contains multibyte characters,
bad things will happen.")

(defun example-text-view-search ()
  (within-main-loop
    (let ((window (make-instance 'gtk-window
                                 :title "Example Text View Search"
                                 :type :toplevel
                                 :default-width 300
                                 :default-height 200))
          (entry (make-instance 'gtk-entry))
          (button (make-instance 'gtk-button
                                 :label "Search"))
          (scrolled (make-instance 'gtk-scrolled-window))
          (text-view (make-instance 'gtk-text-view
                                    :wrap-mode :word
                                    :hexpand t
                                    :vexpand t))
          (vbox (make-instance 'gtk-grid
                               :orientation :vertical))
          (hbox (make-instance 'gtk-grid
                               :orientation :horizontal)))
      (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (leave-gtk-main)))
      ;; Signal handler for the search button
      (g-signal-connect button "clicked"
         (lambda (widget)
           (declare (ignore widget))
           (let* ((text (gtk-entry-text entry))
                  (buffer (gtk-text-view-buffer text-view))
                  (iter (gtk-text-buffer-get-start-iter buffer)))
             (multiple-value-bind (found start end)
                 (gtk-text-iter-search iter text)
               (when found
                 (gtk-text-buffer-select-range buffer start end))))))
      (gtk-text-buffer-set-text (gtk-text-view-buffer text-view)
                                *some-text*)
      (gtk-container-add scrolled text-view)
      (gtk-container-add hbox entry)
      (gtk-container-add hbox button)
      (gtk-container-add vbox hbox)
      (gtk-container-add vbox scrolled)
      (gtk-container-add window vbox)
      (gtk-widget-show-all window))))
@end verbatim

@subheading Continuing the search with Marks

If you had executed the above program you would have noted that, if there were
more than one occurrence of the text in the buffer, pressing search will only
highlight the first occurrence of the text.  To provide a feature similarly to
Find Next; the program has to remember the location where the previous search
stopped.  So that you can start searching from that location. And this should
happen even if the buffer were modified between the two searches.  We could
store the @code{match-end} iter passed on the function
@xurl{gtk-text-iter-search} and use it as the starting point for the next search.
But the problem is that if the buffer were modified in between, the iter would
get invalidated. This takes us to marks.

@findex gtk-text-buffer-create-mark
@findex text-buffer-create-mark, gtk-text-buffer-create-mark

@findex gtk-text-buffer-get-mark
@findex text-buffer-get-mark, gtk-text-buffer-get-mark

@findex gtk-text-buffer-get-iter-at-mark
@findex text-buffer-get-iter-at-mark, gtk-text-buffer-get-iter-at-mark

A mark preserves a position in the buffer between modifications.  This is
possible because their behavior is defined when text is inserted or deleted.
When text containing a mark is deleted, the mark remains in the position
originally occupied by the deleted text. When text is inserted at a mark, a mark
with left gravity will be moved to the beginning of the newly-inserted text, and
a mark with right gravity will be moved to the end.

The gravity of the mark is specified while creation. The function
@xurl{gtk-text-buffer-create-mark} with the arguments @code{buffer},
@code{mark-name}, @code{where} and @code{left-grafity} can be used to create a
mark associated with a buffer.

The iter @code{where} specifies a position in the buffer which has to be marked.
@code{left-gravity} determines how the mark moves when text is inserted at the
mark.  The argument @code{mark-name} is a string that can be used to identify
the mark.  If @code{mark-name} is specified, the mark can be retrieved using the
function @xurl{gtk-text-buffer-get-mark}

With named tags, you do not have to carry around a pointer to the marker, which
can be easily retrieved using the function @xurl{gtk-text-buffer-get-mark}.  A
mark by itself cannot be used for buffer operations, it has to converted into an
iter just before buffer operations are to be performed. The function
@xurl{gtk-text-buffer-get-iter-at-mark} with the arguments @code{buffer} and
@code{mark} returns the iter at the position of @code{mark}.

@subheading The Scrolling Problem

@findex gtk-text-view-scroll-mark-onscreen
@findex gtk-text-view-scroll-mark-onscreen, gtk-text-view-scroll-mark-onscreen

Before we show an example, we have to solve the problem that the text view
should scroll to the matched text.  It can be irritating when the matched text
is not in the visible portion of the buffer.  The function
@xurl{gtk-text-view-scroll-mark-onscreen} with the arguments @code{text-view}
and @code{mark} scrolls to a position in the buffer.  The argument @code{mark}
specifies the position to scroll to.  Note that this is a method of the
@xurl{gtk-text-view} widget rather than a @xurl{gtk-text-buffer} object.  Since
it does not change the contents of the buffer, it only changes the way a buffer
is viewed.

The following example shows the usage of marks to continue the search.

@ifnotinfo
@float Figure, figure-text-view-find-next
@caption{Searching text in a text view.}
@center @image{figures/text-view-find-next, 201pt}
@end float
@end ifnotinfo

@float Example, example-text-view-find-next
@caption{Searching text in a text view.}
@end float
@verbatim
(defun find-text (text-view text iter)
  (let ((buffer (gtk-text-view-buffer text-view)))
    (multiple-value-bind (found start end)
        (gtk-text-iter-search iter text)
      (when found
        (gtk-text-buffer-select-range buffer start end)
        (let ((last-pos (gtk-text-buffer-create-mark buffer "last-pos" end)))
          (gtk-text-view-scroll-mark-onscreen text-view last-pos))))))

(defun example-text-view-find-next ()
  (within-main-loop
    (let ((window (make-instance 'gtk-window
                                 :title "Multiline Text Search"
                                 :type :toplevel
                                 :default-width 450
                                 :default-height 200))
          (entry (make-instance 'gtk-entry))
          (button-search (make-instance 'gtk-button
                                        :label "Search"))
          (button-next (make-instance 'gtk-button
                                      :label "Next"))
          (scrolled (make-instance 'gtk-scrolled-window))
          (text-view (make-instance 'gtk-text-view
                                    :hexpand t
                                    :vexpand t))
          (vbox (make-instance 'gtk-grid
                               :orientation :vertical))
          (hbox (make-instance 'gtk-grid
                               :orientation :horizontal)))
      (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (leave-gtk-main)))
      (g-signal-connect button-search "clicked"
         (lambda (widget)
           (declare (ignore widget))
           (let* ((text (gtk-entry-text entry))
                  (buffer (gtk-text-view-buffer text-view))
                  (iter (gtk-text-buffer-get-start-iter buffer)))
             (find-text text-view text iter))))
      (g-signal-connect button-next "clicked"
         (lambda (widget)
           (declare (ignore widget))
           (let* ((text (gtk-entry-text entry))
                  (buffer (gtk-text-view-buffer text-view))
                  (last-pos (gtk-text-buffer-get-mark buffer "last-pos")))
             (when last-pos
               (find-text text-view
                          text
                          (gtk-text-buffer-get-iter-at-mark buffer
                                                            last-pos))))))
      (gtk-text-buffer-set-text (gtk-text-view-buffer text-view)
                                *some-text*)
      (gtk-container-add scrolled text-view)
      (gtk-container-add hbox entry)
      (gtk-container-add hbox button-search)
      (gtk-container-add hbox button-next)
      (gtk-container-add vbox hbox)
      (gtk-container-add vbox scrolled)
      (gtk-container-add window vbox)
      (gtk-widget-show-all window))))
@end verbatim

@subheading More on Marks

@findex gtk-text-buffer-delete-mark
@findex text-buffer-delete-mark, gtk-text-buffer-delete-mark

@findex gtk-text-buffer-delete-mark-by-name
@findex text-buffer-delete-mark-by-name, gtk-text-buffer-delete-mark-by-name

When a mark is no longer required, it can be deleted using the functions
@xurl{gtk-text-buffer-delete-mark} or
@xurl{gtk-text-buffer-delete-mark-by-name}.
There are two marks built-in to @xurl{gtk-text-buffer} - "insert" and
"selection-bound".  The "insert" mark refers to the cursor position, also called
the insertion point.  A selection is bounded by two marks.  One is the "insert"
mark and the other is "selection-bound" mark.  When no text is selected the two
marks are in the same position.

@c -----------------------------------------------------------------------------
@node Examing and Modify Text, Images and Widgets, Searching, Top
@section Examing and Modify Text

@findex gtk-text-iter-set-line-offset
@findex text-iter-set-line-offset, gtk-text-iter-set-line-offset

@findex gtk-text-iter-forward-to-line-end
@findex text-iter-forward-to-line-end, gtk-text-iter-forward-to-line-end

Examining and modifying text is another common operation performed on text
buffers.  Examples are converting a selected portion of text into a comment
while editing a program, determining and inserting the correct end tag while
editing HTML, inserting a pair of HTML tags around the current word, etc.  The
@xurl{gtk-text-iter} object provides functions to do such processing.

In this section we will develop two programs to demonstrate these functions.
The first program will insert start/end li tags (not to be confused with text
attribute tags) around the current line, when a button is clicked.  The second
program will insert an end tag for an unclosed start tag.

To insert tags around the current line, we first obtain an iter at the current
cursor position. Then we move the iter to the beginning of the line, insert the
start tag, move the iter to the end of the line, and insert the end tag.  An
iter can be moved to a specified offset in the same line using the function
@xurl{gtk-text-iter-set-line-offset} with the arguments @code{iter} and
@code{char-on-line}.  The function moves @code{iter} within the line, to the
character offset specified by @code{char-on-line}.  If @code{char-on-line} is
equal to the no. of characters in the line, the iter is moved to the start of
the next line.  A character offset of zero, will move the iter to the beginning
of the line.  The @code{iter} can be moved to the end of the line using the
function @xurl{gtk-text-iter-forward-to-line-end}.  Now that we know the
functions required to implement the first program, here is the code.

@ifnotinfo
@float Figure, figure-text-view-editing-1
@center @image{figures/text-view-editing-1, 201pt}
@end float
@end ifnotinfo

@float Example, example-text-view-editing-1
@caption{Modifiy text in a text view.}
@end float
@verbatim
(defun example-text-editing-text-1 ()
  (within-main-loop
    (let ((window (make-instance 'gtk-window
                                 :title "Multiline Text Editing"
                                 :type :toplevel
                                 :default-width 300
                                 :default-height 200))
          (text-view (make-instance 'gtk-text-view
                                    :hexpand t
                                    :vexpand t))
          (button (make-instance 'gtk-button
                                 :label "Make List Item"))
          (vbox (make-instance 'gtk-grid
                                :orientation :vertical)))
    (g-signal-connect window "destroy"
                      (lambda (widget)
                        (declare (ignore widget))
                        (leave-gtk-main)))
    (g-signal-connect button "clicked"
       (lambda (widget)
         (declare (ignore widget))
         (let* ((buffer (gtk-text-view-buffer text-view))
                (cursor (gtk-text-buffer-get-mark buffer "insert"))
                (iter (gtk-text-buffer-get-iter-at-mark buffer cursor)))
           (gtk-text-iter-set-line-offset iter 0)
           (gtk-text-buffer-insert buffer "<li>" :position iter)
           (gtk-text-iter-forward-to-line-end iter)
           (gtk-text-buffer-insert buffer "</li>" :position iter))))
   (gtk-text-buffer-set-text (gtk-text-view-buffer text-view)
                             (format nil "Item 1~%Item 2~%Item 3~%"))
   (gtk-container-add vbox text-view)
   (gtk-container-add vbox button)
   (gtk-container-add window vbox)
   (gtk-widget-show-all window))))
@end verbatim

@findex gtk-text-iter-find-char
@findex gtk-text-iter-find-char, gtk-text-iter-find-char

@findex gtk-text-iter-backward-find-char
@findex gtk-text-iter-backward-find-char, gtk-text-iter-backward-find-char

For the second program, we will have to first get the iter at the current cursor
position.  We then search backwards from the cursor position, through the buffer
till we hit on an unclosed tag.  We then insert the corresponding end tag at the
current cursor position.  Note that the procedure given does not take care of
many special cases, and might not be the best way to determine an unclosed tag.
But it serves our purpose of explaining text manipulation functions. Developing
a perfect algorithm to determine an unclosed tag, is out of the scope of this
tutorial.  We can identify tags using the left angle bracket. So searching for
start/end tags involves search for the left angle bracket. This can be done
using the function @xurl{gtk-text-iter-backward-find-char} or the function
@xurl{gtk-text-iter-find-char} with a value @code{:backward} for the keyword
argument @code{direction}.

@findex gtk-text-iter-forward-char
@findex text-iter-forward-char, gtk-text-iter-forward-char

The function proceeds backwards from @code{iter}, and calls @code{pred} for each
character in the buffer, with the character as argument, till @code{pred}
returns @emph{true}. If a match is found, the function moves @code{iter} to the
matching position and returns @emph{true}. If a match is not found, the function
moves @code{iter} to the beginning of the buffer or @code{limit} (if not
@code{nil}) and returns @code{nil}.  For our purpose we write a predicate that
returns @emph{true} when the character is a left angle bracket.  When we hit on
a left angle bracket we check whether the corresponding tag is a start tag or an
end tag.  This is done by examining the character immediately after the left
angle bracket.  If it is a '/' it is an end tag.  To extract the character after
the angle bracket we move the left angle bracket @code{iter} by one character.
And then extract the character at that position.  To move an @code{iter} forward
by one character, the function @xurl{gtk-text-iter-forward-char} can be used.

@findex gtk-text-iter-get-char
@findex text-iter-get-char, gtk-text-iter-get-char

To extract the character at an @code{iter} the function
@xurl{gtk-text-iter-get-char} can be used.  After determining the tag type we do
the following,
@itemize @bullet
  @item If the tag is an end tag, we push the tag name into a stack and then
        proceed to find more tags.
  @item If it is a start tag, we pop out it's matching tag from the stack. While
        poping out, if there were no more items in the stack, we have hit on an
        unmatched start tag! We then insert the corresponding end tag at the
        current cursor position.
@end itemize
We have not mentioned how we extract the tag name.  The tag name is extracted
using two iters (start and end iter).  The start iter is obtained by starting
from the left angle bracket iter and searching for an alphanumeric character,
in the forward direction.  The end iter is obtained by starting from the start
iter and searching for a non-alphanumeric character, in the forward direction.
The search can be done using the forward variant of the the function
@xurl{gtk-text-iter-backward-find-char}.  The code for the second example
follows.

@ifnotinfo
@float Figure, figure-text-view-editing-2
@center @image{figures/text-view-editing-2, 201pt}
@end float
@end ifnotinfo

@float Example, example-text-view-editing-2
@caption{Modifiy text in a text view.}
@end float
@verbatim
(defun get-this-tag (iter buffer)
  (let* ((start-tag (gtk-text-iter-copy iter))
         end-tag)
    (and (gtk-text-iter-find-char start-tag #'alpha-char-p)
         (setq end-tag (gtk-text-iter-copy start-tag))
         (gtk-text-iter-find-char end-tag
                                  (lambda (ch) (not (alphanumericp ch))))
         (gtk-text-buffer-get-text buffer start-tag end-tag nil))))

(defun closing-tag-p (iter)
  (let ((slash (gtk-text-iter-copy iter)))
    (gtk-text-iter-forward-char slash)
    (eql (gtk-text-iter-get-char slash) #\/)))

(defun example-text-view-editing-2 ()
  (within-main-loop
    (let ((window (make-instance 'gtk-window
                                 :title "Multiline Editing Text"
                                 :type :toplevel
                                 :default-width 300
                                 :defalut-height 200))
          (text-view (make-instance 'gtk-text-view
                                    :hexpand t
                                    :vexpand t))
          (button (make-instance 'gtk-button
                                 :label "Insert Close Tag"))
          (vbox (make-instance 'gtk-grid
                               :orientation :vertical)))
      (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (leave-gtk-main)))
      (g-signal-connect button "clicked"
         (lambda (widget)
           (declare (ignore widget))
           (let* ((buffer (gtk-text-view-buffer text-view))
                  (cursor (gtk-text-buffer-get-mark buffer "insert"))
                  (iter (gtk-text-buffer-get-iter-at-mark buffer cursor)))

             (do ((stack '()))
                 ((not (gtk-text-iter-find-char iter
                                               (lambda (ch) (eq ch #\<))
                                               :direction :backward)))
               (let ((tag (get-this-tag iter buffer)))
                 (if (closing-tag-p iter)
                     (push tag stack)
                     (let ((tag-in-stack (pop stack)))
                       (when (not tag-in-stack)
                         (gtk-text-buffer-insert buffer
                                                 (format nil "</~a>" tag))
                         (return)))))))))
      (gtk-text-buffer-set-text (gtk-text-view-buffer text-view)
                                (format nil
                                        "<html>~%~
                                         <head><title>Title</title></head>~%~
                                         <body>~%~
                                         <h1>Heading</h1>~%"))
      (gtk-container-add vbox text-view)
      (gtk-container-add vbox button)
      (gtk-container-add window vbox)
      (gtk-widget-show-all window))))
@end verbatim

@c -----------------------------------------------------------------------------
@node Images and Widgets, Buffer and Window Coordinates, Examing and Modify Text, Top
@section Images and Widgets

@subheading Inserting Images

A text buffer can hold images and anchor location for widgets.  An image can be
inserted into a buffer using the function @xurl{gtk-text-buffer-insert-pixbuf}
with the arguments @code{buffer}, @code{iter}, and @code{pixbuf}.  An image
represented by @code{pixbuf} is inserted at @code{iter}. The @code{pixbuf} can
be created from an image file using the function
@xurl{gdk-pixbuf-new-from-file}. See the API documentation for @xurl{gdk-pixbuf}
for more details.

The example program given below takes in an image filename and inserts the
corresponding image into a buffer.

@ifnotinfo
@float Figure, figure-text-view-insert-image
@center @image{figures/text-view-insert-image, 201pt}
@end float
@end ifnotinfo

@float Example, example-text-insert-image
@caption{Insert an image.}
@end float
@verbatim
(defun example-text-view-insert-image ()
  (within-main-loop
    (let ((window (make-instance 'gtk-window
                                 :type :toplevel
                                 :title "Multiline Text Widget"
                                 :default-width 300
                                 :default-height 200))
          (text-view (make-instance 'gtk-text-view
                                    :hexpand t
                                    :vexpand t))
          (button (make-instance 'gtk-button
                                 :label "Insert Image"))
          (vbox (make-instance 'gtk-grid
                               :orientation :vertical)))
    (g-signal-connect window "destroy"
                      (lambda (widget)
                        (declare (ignore widget))
                        (leave-gtk-main)))
    ;; Signal handler to insert an image at the current cursor position.
    (g-signal-connect button "clicked"
       (lambda (widget)
         (declare (ignore widget))
         (let* ((pixbuf (gdk-pixbuf-new-from-file "save.png"))
                (buffer (gtk-text-view-buffer text-view))
                (cursor (gtk-text-buffer-get-insert buffer))
                (iter (gtk-text-buffer-get-iter-at-mark buffer cursor)))
           (gtk-text-buffer-insert-pixbuf buffer iter pixbuf))))
    (gtk-container-add vbox text-view)
    (gtk-container-add vbox button)
    (gtk-container-add window vbox)
    (gtk-widget-show-all window))))
@end verbatim

@subheading Retrieving Images

Images in a buffer are represented by the character @code{0xFFFC} (Unicode
object replacement character).  When text containing images is retrieved from
a buffer using the function @xurl{gtk-text-buffer-get-text} the @code{0xFFFC}
characters representing images are dropped off in the returned text. If these
characters representing images are required, use the slice variant -
@xurl{gtk-text-buffer-get-slice}.  The image at a given position can be
retrieved using the function @xurl{gtk-text-iter-get-pixbuf}.

@subheading Inserting Widgets

Inserting a widget, unlike inserting an image, is a two step process. The
additional complexity is due to the functionality split between
@xurl{gtk-text-view} and @xurl{gtk-text-buffer}.  The first step is to create
and insert a @xurl{gtk-text-child-anchor}.  A widget is held in a buffer using a
@xurl{gtk-text-child-anchor}.  A child anchor according to the GTK manual is a
spot in the buffer where child widgets can be anchored.  A child anchor can be
created and inserted into a buffer using the function
@xurl{gtk-text-buffer-create-child-anchor} with the arguments @code{buffer} and
@code{iter}.  Where @code{iter} specifies the position in the buffer, where the
widget is to be inserted.  The next step is to add a child widget to the text
view, at the anchor location with the function
@xurl{gtk-text-view-add-child-at-anchor}.

An anchor can hold only one widget, it could be a container widget, which in
turn can contain many widgets, unless you are doing tricky things like
displaying the same buffer using different @xurl{gtk-text-view} objects.  The
following program inserts a button widget into a text buffer, whenever the user
clicks on the Insert button.

@ifnotinfo
@float Figure, figure-text-view-insert-widget
@center @image{figures/text-view-insert-widget, 201pt}
@end float
@end ifnotinfo

@float Example, example-text-insert-widget
@caption{Insert a widget.}
@end float
@verbatim
(defun example-text-view-insert-widget ()
  (within-main-loop
    (let ((window (make-instance 'gtk-window
                                 :type :toplevel
                                 :title "Multiline Text Widget"
                                 :default-width 300
                                 :default-height 200))
          (text-view (make-instance 'gtk-text-view
                                    :hexpand t
                                    :vexpand t))
          (button (make-instance 'gtk-button
                                 :label "Insert Widget"))
          (vbox (make-instance 'gtk-grid
                               :orientation :vertical)))
    (g-signal-connect window "destroy"
                      (lambda (widget)
                        (declare (ignore widget))
                        (leave-gtk-main)))
    ;; Signal handler to insert a widget at the current cursor position.
    (g-signal-connect button "clicked"
       (lambda (widget)
         (declare (ignore widget))
         (let* ((buffer (gtk-text-view-buffer text-view))
                (cursor (gtk-text-buffer-get-insert buffer))
                (iter (gtk-text-buffer-get-iter-at-mark buffer cursor))
                (anchor (gtk-text-buffer-create-child-anchor buffer iter))
                (button (gtk-button-new-with-label "New Button")))
           (gtk-text-view-add-child-at-anchor text-view button anchor)
           (gtk-widget-show button))))
    (gtk-container-add vbox text-view)
    (gtk-container-add vbox button)
    (gtk-container-add window vbox)
    (gtk-widget-show-all window))))
@end verbatim

@subheading Retrieving Widgets

Child anchors are also represented in the buffer using the object replacement
character @code{0xFFFC}.  Retrieving a widget is also a two step process.
First, the child anchor has to be retrieved.  This can be done using the
function @xurl{gtk-text-iter-get-child-anchor}.  Next, the widget(s) associated
with the child anchor has to be retrieved.  This can be done using the function
@xurl{gtk-text-child-anchor-get-widgets}.  The function returns a list of
widgets.  As mentioned earlier, if you are not doing tricky things like multiple
views for the same buffer, you will find only one widget in this list.

@c -----------------------------------------------------------------------------
@node Buffer and Window Coordinates, Final Notes, Images and Widgets, Top
@section Buffer and Window Coordinates

Sometimes it is necessary to know the position of the text cursor on the screen,
or the word in a buffer under the mouse cursor.  For example, when you want to
display the prototype of a function as a tooltip, when the user types open
parenthesis.  To do this, you will have to understand buffer coordinates and
window coordinates.

Both the buffer and window coordinates are pixel level coordinates. The
difference is that the window coordinates takes into account only the portion of
the buffer displayed on the screen.  The concept would be better explained using
a diagram.  The large white box(with grid lines) in the following diagram
depicts the text buffer.  And the smaller inner grey box is the visible portion
of the text buffer, displayed by the text view widget.

@ifnotinfo
@float Figure, figure-buffercoord
@center @image{figures/buffercoord, 123pt}
@end float
@end ifnotinfo

The buffer coordinates of the red dot (represented as (x, y)) is (4, 3).  But
the window coordinates of the red dot is (2, 1).  This is because the window
coordinates are calculated relative the visible portion of the text buffer.
Similarly, the buffer coordinates of the blue dot is (3, 5) and the window
coordinates is (1, 3).

In this section, you will learn how to display tooltips under the text cursor.
The procedure is as follows,
@itemize @bullet
  @item The buffer coordinates of the text cursor is obtained.
  @item The buffer coordinates is converted to window coordinates (x1, y1).
  @item The position (x2, y2) of the text view widget on the screen is obtained.
  @item A window with the tooltip is displayed at (x1+x2, y1+y2).
@end itemize
The buffer coordinates of a particular character in a buffer can be obtained
using the function @xurl{gtk-text-view-get-iter-at-location} with the argument
@code{iter}.  The function gets the rectangle that contains the character at
@code{iter} and returns it.  The @code{x} and @code{y} members of
@code{location} gives us the buffer coordinates.

The buffer coordinates can be converted into window coordinates using the
function @xurl{gtk-text-view-buffer-to-window-coords}.  The function converts
buffer coordinates @code{(buffer-x, buffer-y)}, to window coordinates
@code{(window-x, window-y)}.

Now that we know the position of the character within the text view widget, we
will have to find the position of the text view widget on the screen.

Each GTK widget has a corresponding @xurl{gdk-window} associated with it.  Once
we know the @xurl{gdk-window} associated with a widget, we can obtain it's X-Y
coordinates using @xurl{gdk-window-get-origin}.  The @xurl{gdk-window} of the
text view widget can be obtained using the function
@xurl{gtk-text-view-get-window}.  Here again you will have to pass
@code{:widget} for the argument @code{win}.

We now know the functions required to display a tooltip under the text cursor.
Before we proceed to the example, you will have to know which signal has to be
trapped to do display the tooltip. Since we want the tooltip to be displayed
when the user inserts open parenthesis, "insert-text" emitted by the buffer
object can be used.  As the signal's name suggests it is called whenever the
user inserts text into the buffer.  The callback prototype is
@code{lambda (buffer pos text length)}.

The function is called with @code{position} after the inserted text @code{pos},
the inserted text @code{text} and the length of the inserted text @code{length}.

Below is an example program that displays a tooltip for the printf family of
functions.

@ifnotinfo
@float Figure, figure-text-view-tooltip
@center @image{figures/text-view-tooltip, 201pt}
@end float
@end ifnotinfo

@float Example, example-text-view-tooltip
@caption{Show tooltips in a Text View.}
@end float
@verbatim
(let ((tooltip nil))
  (defun get-tip (word)
    (cdr (assoc word
                '(("printf" . "(const char *format, ...)")
                  ("fprintf" . "(FILE *stream, const char *format, ...)")
                  ("sprintf" . "(char *str, const char *format, ...)")
                  ("fputc" . "(int c, FILE *stream)")
                  ("fputs" . "(const char *s, FILE *stream)")
                  ("putc" . "(int c, FILE *stream)")
                  ("putchar" . "(int c)")
                  ("puts" . "(const char *s)"))
                :test #'equal)))

  (defun tip-window-new (tip-text)
    (let ((win (make-instance 'gtk-window
                              :type :popup
                              :border-width 0))
          (event-box (make-instance 'gtk-event-box
                                    :border-width 1))
          (label (make-instance 'gtk-label
                                :label tip-text)))
      (gtk-widget-override-font
          label
          (pango-font-description-from-string "Courier"))
      (gtk-widget-override-background-color win
                                            :normal
                                            (gdk-rgba-parse "Black"))
      (gtk-widget-override-color win :normal (gdk-rgba-parse "Blue"))
      (gtk-container-add event-box label)
      (gtk-container-add win event-box)
      win))

  (defun insert-open-brace (window text-view location)
    (let ((start (gtk-text-iter-copy location)))
      (when (gtk-text-iter-backward-word-start start)
        (let* ((word (string-trim " "
                                  (gtk-text-iter-get-text start location)))
               (tip-text (get-tip word)))
          (when tip-text
            (let ((rect (gtk-text-view-get-iter-location text-view location))
                  (win (gtk-text-view-get-window text-view :widget)))
              (multiple-value-bind (win-x win-y)
                  (gtk-text-view-buffer-to-window-coords
                      text-view
                      :widget
                      (gdk-rectangle-x rect)
                      (gdk-rectangle-y rect))
                (multiple-value-bind (x y)
                    (gdk-window-get-origin win)
                  ;; Destroy any previous tool tip window
                  (when tooltip
                    (gtk-widget-destroy tooltip)
                    (setf tooltip nil))
                  ;; Create a new tool tip window
                  (setf tooltip (tip-window-new tip-text))
                  ;; Place it at the calculated position.
                  (gtk-window-move tooltip
                                   (+ win-x x)
                                   (+ win-y y (gdk-rectangle-height rect)))
                  (gtk-widget-show-all tooltip)))))))))

  (defun example-text-view-tooltip ()
    (within-main-loop
      (let* ((window (make-instance 'gtk-window
                                    :title "Multiline Text Search"
                                    :type :toplevel
                                    :default-width 450
                                    :default-height 200))
             (scrolled (make-instance 'gtk-scrolled-window))
             (text-view (make-instance 'gtk-text-view
                                       :hexpand t
                                       :vexpand t))
             (buffer (gtk-text-view-buffer text-view)))
        ;; Signal handler for the window
        (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (when tooltip
                            (gtk-widget-destroy tooltip)
                            (setf tooltip nil))
                          (leave-gtk-main)))
        ;; Signal handler for the buffer of the text view
        (g-signal-connect buffer "insert-text"
           (lambda (buffer location text len)
             (declare (ignore buffer len))
             (when (equal text "(")
               (insert-open-brace window text-view location))
             (when (equal text ")")
               (when tooltip
                 (gtk-widget-destroy tooltip)
                 (setf tooltip nil)))))
        ;; Change the default font
        (gtk-widget-override-font
            text-view
            (pango-font-description-from-string "Courier 12"))
        ;; Add the widgets to window and show all
        (gtk-container-add scrolled text-view)
        (gtk-container-add window scrolled)
        (gtk-widget-show-all window)))))
@end verbatim

@subheading More on Buffer and Window Coordinates

In the previous section we obtained the screen coordinates for a position in the
buffer.  What if we want to do the exact opposite, i. e. what if we want to find
the position in the buffer corresponding to a particular X-Y coordinate.  The
@xurl{gtk-text-view} has functions for these as well.

Window coordinates can be converted to buffer coordinates using the function
@xurl{gtk-text-view-window-to-buffer-coords}.

The iter at a buffer coordinate can be obtained using the function
@xurl{gtk-text-view-get-iter-at-location}.

@c -----------------------------------------------------------------------------
@node Final Notes, , Buffer and Window Coordinates, Top
@section Final Notes


