PreviousUpNext

15.4.1548  src/lib/x-kit/widget/old/text/text-widget.pkg

## text-widget.pkg
#
# A simple text widget: currently this only supports one fixed-width font (9x15).

# Compiled by:
#     src/lib/x-kit/widget/xkit-widget.sublib





###             "[Television] won't be able to hold on
###              to any market it captures after the
###              first six months.
###
###             "People will soon get tired of staring
###              at a plywood box every night."
###
###                       -- Darryl F Zanuck, 1946      (Movie studio head + producer.) 


stipulate
    include package   threadkit;                # threadkit     is from   src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg
    #
    package g2d =  geometry2d;                  # geometry2d    is from   src/lib/std/2d/geometry2d.pkg
    package xc  =  xclient;                     # xclient       is from   src/lib/x-kit/xclient/xclient.pkg
    #
    package wg  =  widget;                      # widget        is from   src/lib/x-kit/widget/old/basic/widget.pkg
herein

    # This package is used in:
    #
    #     src/lib/x-kit/demo/tactic-tree/src/manager-g.pkg
    #     src/lib/x-kit/widget/old/text/virtual-terminal.pkg
    #
    # Also, these three mention text_widget::Char_Point:
    #     src/lib/x-kit/widget/old/fancy/graphviz/text/text-display.pkg
    #     src/lib/x-kit/widget/old/fancy/graphviz/text/text-canvas.pkg
    #     src/lib/x-kit/widget/old/fancy/graphviz/text/view-buffer.pkg

    package text_widget
    :       Text_Widget                         # Text_Widget   is from   src/lib/x-kit/widget/old/text/text-widget.api
    {
        caextract = rw_vector_slice_of_chars::to_vector
                    o
                    rw_vector_slice_of_chars::make_slice;

        fun impossible (f, msg)
            =
            raise exception lib_base::IMPOSSIBLE("text_widget." + f + ": " + msg);

        Char_Point
            =
            CHAR_POINT { col:  Int,
                         row:  Int
                       };

        fun min (a:  Int, b) =   a < b  ??  a  ::  b;
        fun max (a:  Int, b) =   a > b  ??  a  ::  b;

        font_name = "9x15";
        pad = 2;
        tot_pad = pad+pad;

        # Get the character dimensions from a (fixed-width) font 
        #
        fun font_info font
            =
            {   (xc::font_high font)
                    ->
                    { ascent, descent };

                (ascent + descent, xc::text_width font "M", ascent);
            };

        # A description of the various size parameters of a text window 
        #
        Text_Size
            =
            TEXT_SIZE  {
              size:       g2d::Size,
              #
              rows:       Int,
              cols:       Int,
              #
              char_high:  Int,
              char_wide:  Int,
              #
              ascent:     Int
            };

        #  Make a text window size descriptor from a window size and font. 
        #
        fun make_text_size (window_size as { wide, high }, font)
            =
            {   (font_info font)
                    ->
                    (char_high, char_wide, ascent);

                TEXT_SIZE {
                    size => window_size,
                    rows => int::quot (high - tot_pad, char_high),
                    cols => int::quot (wide - tot_pad, char_wide),
                    char_high,
                    char_wide,
                    ascent
                  };
              };

        # Return TRUE if the character coordinate is in the text window 
        #
        fun in_text_window (TEXT_SIZE { rows, cols, ... }, CHAR_POINT { row, col } )
            =
            ((0 <= row) and (row < rows)) and
            ((0 <= col) and (col < cols));

        # Clip a string to insure that it does not exceed the text length 
        #
        fun clip_string (TEXT_SIZE { cols, ... }, col, s)
            =
            {   len = string::length_in_bytes s;
                #
                col + len  <=  cols   ??   s
                                      ::   substring (s, 0, cols-col);
            };


        # *** The text buffer ***
        # This is a two dimensional array of characters with highlighting information.
        #
        stipulate
            Text_Line
                =
                TEXT_LINE
                  ( rw_vector_of_chars::Rw_Vector,
                    List ((Int, Int))                                   # Highlight-region list.  Each pairs gives (col, len?) of one highlighted region.
                  );
        herein

            stipulate
                Text_Buf                                                # Start abstype-replacement recipe -- see http://successor-ml.org/index.php?title=Degrade_abstype_to_derived_form 
                    =                                                   #
                    TEXT_BUF                                            #
                      { size:  g2d::Size,                               #
                        arr:   rw_vector::Rw_Vector( Text_Line )        #
                      };                                                #
            herein                                                      #
                Text_Buf = Text_Buf;                                    # End of abstype-replacement recipe.

                stipulate

                    # Reverse first arg and prepend it to second arg:
                    #
                    fun reverse_and_prepend ([],    l) =>  l;
                        reverse_and_prepend (x ! r, l) =>  reverse_and_prepend (r, x ! l);
                    end;

                    # Update the highlight region list of a line to reflect the writing of a
                    # length "len" normal-mode string starting in column "col".

                    fun ins_n (_, _, [] : List( (Int, Int) ) )                                          # "ins_n" == "insert normal" (non-highlighted) text.  Except we're overwriting, not inserting.
                            =>
                            [];

                        ins_n (col, len, format)
                            =>
                            prefix (format, [])
                            where
                                end_col = col+len;
                                #
                                fun prefix ([], _)
                                        =>
                                        format;                                                         # The written text falls after all highlight regions 

                                    prefix ((c, n) ! r, l)
                                        =>
                                        {   end_c = c+n;
                                            #
                                            if (end_c <= col) 
                                                #
                                                prefix (r, (c, n) ! l);                                 # This highlighted region is unaffected by the written text because it is entirely before the insertion point.

                                            elif (end_col <= c)
                                                #
                                                format;                                                 # Written text is not within any highlighted region.

                                            elif (c < col)

                                                if (end_c <= end_col)
                                                    #
                                                    suffix ((c, col-c) ! l, r);
                                                else
                                                    reverse_and_prepend (l, (c, col-c) ! (end_col, end_c-end_col) ! r);
                                                fi;

                                            elif (end_c <= end_col)
                                                #
                                                suffix (l, r);                                          # Inserted text covers highlighed region (c, n) 
                                            else
                                                reverse_and_prepend (l, (end_col, end_c-end_col) ! r);
                                            fi;
                                        };
                                end  
                                also
                                fun suffix (pre, [])
                                        =>
                                        reverse_and_prepend (pre, []);

                                    suffix (pre, (c, n) ! r)
                                        =>
                                        {   end_c = c+n;
                                            #
                                            if (end_c <= end_col)
                                                #
                                                suffix (pre, r);

                                            elif (c < end_col)
                                                #
                                                reverse_and_prepend (pre, (end_col, end_c-end_col) ! r);
                                            else
                                                reverse_and_prepend (pre, r);
                                            fi;
                                        };
                                end;
                            end;                                        #  fun ins_n
                    end;

                    # Update the highlight region list of a line to reflect the writing of a
                    # length len highlighted string starting in column col.
                    #
                    fun ins_h (col, len, [] : List( (Int, Int) ) )                                      # "ins_h" == "insert highlighted" text.  Except we're overwriting, not inserting.
                            =>
                            [(col, len)];

                        ins_h (col, len, format)
                            =>
                            {
                                end_col = col+len;
                                #
                                fun prefix ([], l)
                                        =>
                                        reverse_and_prepend (l, [(col, len)]);

                                    prefix ((c, n) ! r, l)
                                        =>
                                        {   end_c = c+n;
                                            #
                                            if (end_c < col)
                                                #
                                                prefix (r, (c, n) ! l);

                                            elif (end_col < c)
                                                #
                                                reverse_and_prepend (l, (col, len) ! (c, n) ! r);

                                            elif (c < col)
                                                #
                                                if (end_c < end_col)
                                                    #
                                                    suffix (l, c, end_col, r);
                                                else
                                                    format;
                                                fi;

                                            elif (end_c < end_col)
                                                #
                                                suffix (l, col, end_col, r);
                                            else
                                                reverse_and_prepend (l, (col, end_c-col) ! r);

                                            fi;
                                      };
                                end 

                                also
                                fun suffix (pre, col, end_col, [])
                                        =>
                                        reverse_and_prepend (pre, [(col, end_col-col)]);

                                    suffix (pre, col, end_col, (c, n) ! r)
                                        =>
                                        {   end_c = c+n;
                                            #
                                            if (c > end_col)
                                                #
                                                reverse_and_prepend (pre, (col, end_col-col) ! (c, n) ! r);

                                            elif (end_c < end_col)
                                                #
                                                suffix (pre, col, end_col, r);
                                            else
                                                reverse_and_prepend (pre, (col, end_c-col) ! r);
                                            fi;
                                       };
                                end;

                                prefix (format, []);

                          };            # fun ins_h clause
                    end;                # fun ins_h

                    fun left_shift (col, delta, format)
                        =
                        {   end_col = col + delta;
                            #
                            fun filter []
                                    =>
                                    [];

                                filter ((c, n) ! r)
                                    =>
                                    {   endc = c+n;
                                        #       
                                        if (c < col)
                                            #
                                            if (endc <= col)
                                                #
                                                (c, n) ! (filter r);

                                            elif (endc <= end_col)

                                                (c, col-c) ! (filter r);
                                            else
                                                (c, col-c) ! (end_col-delta, endc-end_col) ! (filter r);
                                            fi;

                                        elif (c < end_col)

                                            if (endc <= end_col)
                                                #
                                                filter r;
                                            else
                                                (end_col-delta, endc-end_col) ! (filter r);
                                            fi;
                                        else
                                            (c-delta, n) ! (map (\\ (c, n) = (c-delta, n)) r);
                                        fi;
                                    };
                            end;

                            filter format;
                        };                              # fun left_shift 

                    fun right_shift (col, end_col, delta, format)
                        =
                        filter format
                        where
                            fun filter []
                                    =>
                                    [];

                                filter ((c, n) ! r)
                                    =>
                                    if (c+n <= col)
                                        #
                                        (c, n) ! (filter r);
                                    else
                                        if (c < col)
                                            #
                                            (c, min (n+delta, end_col-c)) ! (filter r);
                                        else
                                            c' = c + delta; 

                                            if (c' < end_col)
                                                #
                                                (c', min (n, end_col-c')) ! (filter r);
                                            else
                                                [];
                                            fi;
                                       fi;
                                    fi;
                            end;
                        end;                            # fun right_shift 

                    fun new_text_ln cols
                        =
                        TEXT_LINE (rw_vector_of_chars::make_rw_vector (cols, ' '), []);

                    # Write a string into a bytearray starting at col. 
                    #
                    fun write_string (ba, col, str)
                        =
                        {   fun cpy (i, j)
                                =
                                {   rw_vector_of_chars::set (ba, i, string::get_byte_as_char (str, j));
                                    #
                                    cpy (i+1, j+1);
                                };

                            (cpy (col, 0))
                            except
                                _ = ();
                        };

                    # Copy a block of nchars from fromcol to tocol.
                    # NOTE: the updating of the highlight list is not exact, as
                    # we assume copyText is followed by a clearLine or a writeText, 
                    # which will restore consistency.
                    # we also assume that all characters to the right of 
                    # min (fromcol, tocol) are affected.
                    #
                    fun copy_text (TEXT_BUF { arr, size=>{ wide, ... }}, row, fromcol, tocol, nchars)
                        =
                        {   (rw_vector::get (arr, row))
                                ->
                                TEXT_LINE (ba, format);


                            delta = tocol - fromcol;

                            fun copy_string (0, _, _)
                                    =>
                                    ();
                                    #
                                copy_string (count, index, inc)
                                    =>
                                    {   rw_vector_of_chars::set (ba, index+delta, rw_vector_of_chars::get (ba, index));
                                        copy_string (count - 1, index+inc, inc);
                                    };
                            end;

                            if (delta > 0)
                                #
                                copy_string (nchars, fromcol+nchars - 1, -1);
                                rw_vector::set (arr, row, TEXT_LINE (ba, right_shift (fromcol, wide, delta, format)));
                            else
                                copy_string (nchars, fromcol, 1);
                                rw_vector::set (arr, row, TEXT_LINE (ba, left_shift (tocol, -delta, format)));
                            fi;
                        };

                herein  # reverse_and_prepend

                    # Create a text buffer of the specified size:
                    #
                    fun make_text_buf (TEXT_SIZE { rows, cols, ... } )
                        =
                        loop (rows, [])
                        where
                            fun loop (0, l) =>  TEXT_BUF { size=>{ wide=>cols, high=>rows }, arr=>rw_vector::from_list l };
                                loop (i, l) =>  loop (i - 1, (new_text_ln cols) ! l);
                            end;
                        end;

                    # Write a string in normal mode into a text rw_vector:
                    #
                    fun write_ntext (TEXT_BUF { arr, ... }, row, col, str)
                        =
                        {   (rw_vector::get (arr, row))
                                  ->
                                  TEXT_LINE (ba, format);

                            write_string (ba, col, str);
                            rw_vector::set (arr, row, TEXT_LINE (ba, ins_n (col, string::length_in_bytes str, format)));
                        };

                    # Write a string in highlighted mode into a text rw_vector:
                    #
                    fun write_htext (TEXT_BUF { arr, ... }, row, col, str)
                        =
                        {   my TEXT_LINE (ba, format) = rw_vector::get (arr, row);

                            write_string (ba, col, str);
                            rw_vector::set (arr, row, TEXT_LINE (ba, ins_h (col, string::length_in_bytes str, format)));
                        };

                    # Insert a string into a text rw_vector, shifting chars to the right:
                    #
                    fun insert_buf_text (
                          tbuf as TEXT_BUF { size=>{ wide, ... }, ... }, row, col, str, highlight
                        )
                        =
                        {   slen = size str;

                            eolcnt = wide - col - slen;

                            if (eolcnt > 0)
                                copy_text (tbuf, row, col, col+slen, eolcnt);
                            fi; 

                            highlight   ??   write_htext (tbuf, row, col, str)
                                        ::   write_ntext (tbuf, row, col, str);
                          };

                    #  Clear the given line of text 
                    #
                    fun clear_text_ln (TEXT_BUF { arr, ... }, CHAR_POINT { row, col } )
                        =
                        {   (rw_vector::get (arr, row))
                                ->
                                TEXT_LINE (ba, format);

                            if (col == 0)
                                #
                                rw_vector::set (arr, row, new_text_ln (rw_vector_of_chars::length ba));
                            else
                                fun clr i
                                    =
                                    {   rw_vector_of_chars::set (ba, i, ' ');
                                        clr (i+1);
                                    };

                                new_format
                                    =
                                    ins_n (col, (rw_vector_of_chars::length ba) - col, format);

                                (clr col)
                                except
                                    _ = ();

                                rw_vector::set (arr, row, TEXT_LINE (ba, new_format));
                            fi;
                        };

                    #  Delete count chars at the given position 
                    #
                    fun delete_text_chars (tbuf as TEXT_BUF { size=>{ wide, ... }, ... }, row, col, count)
                        =
                        {   eolcnt = wide - col - count;
                            #
                            if (eolcnt > 0)
                                 #
                                 copy_text (tbuf, row, col+count, col, eolcnt); 

                                 clear_text_ln (tbuf, CHAR_POINT { row, col => wide-count } );
                            else clear_text_ln (tbuf, CHAR_POINT { row, col             } );
                            fi;
                        };

                    #  Clear the given block of text 
                    #
                    fun clear_text { text => TEXT_BUF { arr=>ar, ... }, from, to }
                        =
                        loop from
                        where
                            cols =  {   (rw_vector::get (ar, 0)) ->   TEXT_LINE (ba, _);
                                        #
                                        rw_vector_of_chars::length ba;
                                    };

                            fun clear_ln i
                                =
                                rw_vector::set (ar, i, new_text_ln cols);

                            fun loop i
                                =
                                if (i < to)
                                    #
                                    clear_ln i;
                                    loop (i+1);
                                fi;
                        end;

                    # Move a block of text up; "from" is the bottom of the text to be moved,
                    # "to" is the line to move "from" to, and "nlines" is the size of the
                    # block being moved.  It is assumed that the top line of the moved
                    # block will end up at the top of the screen.
                    #
                    fun move_text_up { text as TEXT_BUF { arr=>ar, ... }, from, to, nlines }
                        =
                        {   fun copy (i, j)
                                =
                                if (i <= to)
                                    #
                                    rw_vector::set (ar, i, rw_vector::get (ar, j));
                                    copy (i+1, j+1);
                                fi;

                            copy (0, from-to);

                            clear_text { text, from => to+1, to => from+1 };
                        };

                    # Move a block of text down; "from" is the top of the text to be moved,
                    # "to" is the line to move "from" to, and "nlines" is the size of the
                    # block being moved.  It is assumed that the bottom line of the moved
                    # block will end up at the bottom of the screen.
                    #
                    fun move_text_down { text as TEXT_BUF { arr=>ar, ... }, from, to, nlines }
                        =
                        {   rows = rw_vector::length ar;
                            #
                            fun copy (i, j)
                                =
                                if (i >= to)
                                    #
                                    rw_vector::set (ar, i, rw_vector::get (ar, j));
                                    copy (i - 1, j - 1);
                                fi;

                            copy (rows - 1, (from + nlines) - 1);
                            clear_text { text, from, to };
                          };

                    # Delete a block of text; "from" is the start of the block, "nlines" is the
                    # number of lines to delete.  The text below the delete block is scrolled up
                    # to fill the space, with blank lines filling from the bottom.
                    #
                    fun delete_text { text as TEXT_BUF { arr=>ar, ... }, from, nlines }
                        =
                        {   rows =  rw_vector::length ar;
                            #
                            fun copy (i, j)
                                =
                                if (j < rows)
                                    #
                                    rw_vector::set (ar, i, rw_vector::get (ar, j));
                                    copy (i+1, j+1);
                                fi;

                                copy (from, from+nlines);

                                clear_text { text, from => rows-nlines, to => rows };
                          };

                    # Extract the text starting in column "col" of length "len" in row "row".
                    # This is returned as a list of strings: the first in normal mode, the
                    # second in highlighted mode, the third in normal, etc.
                    #
                    fun explode_row { text => TEXT_BUF { arr=>text, ... }, row, col, len }
                        =
                        case (rw_vector::get (text, row))
                            #
                            TEXT_LINE (ba, [])
                                =>
                                [caextract (ba, col, THE len)];

                            TEXT_LINE (ba, l)
                                =>
                                {   end_col =  col + len;
                                    #
                                    fun ext (col, len)
                                        =
                                        caextract (ba, col, THE len);

                                    fun prefix []
                                            =>
                                            [ext (col, len)];

                                        prefix ((c, n) ! r)
                                            =>
                                            {   end_c = c+n;
                                                #
                                                if (end_c <= col)
                                                    #
                                                    prefix r;

                                                elif (end_col <= c)
                                                    #
                                                    [ext (col, len)];

                                                elif (c < col)
                                                    #
                                                    if (end_c < end_col)
                                                        #
                                                        suffix (end_c, r, [ext (col, end_c-col), ""]);
                                                    else
                                                        ["", ext (col, len)];
                                                    fi;
                                                else
                                                    if (end_c < end_col)
                                                        #
                                                        suffix (end_c, r, [ext (c, n), ext (col, c-col)]);
                                                    else
                                                        [ext (col, c-col), ext (c, end_col-c)];
                                                    fi;
                                                fi;
                                            };
                                    end 

                                    also
                                    fun suffix (i, [], l)
                                            =>
                                            reverse_and_prepend (l, [ext (i, end_col-i)]);

                                       suffix (i, (c, n) ! r, l)
                                           =>
                                           {    end_c = c+n;
                                                #
                                                if (end_col <= c)
                                                    #
                                                    reverse_and_prepend (l, [ext (i, end_col-i)]);

                                                elif (end_c < end_col)
                                                    #
                                                    suffix (end_c, r, ext (c, n) ! ext (i, c-i) ! l);
                                                else
                                                    reverse_and_prepend (l, [ext (i, c-i), ext (c, end_col-c)]);
                                                fi;
                                          };
                                    end;

                                    prefix l;
                               };
                        esac;


                    # Resize a text buffer.  If the new size is smaller, then stuff is
                    # dropped from the bottom and right.  If the new size is larger, then
                    # blank space is added to the bottom and right.
                    #
                    fun resize_text_buf (TEXT_BUF { arr=>old_a, ... }, new_size as TEXT_SIZE { rows, cols, ... } )
                        =
                        new_tb
                        where
                            (make_text_buf new_size)
                                ->
                                (new_tb as (TEXT_BUF { arr=>new_a, ... } ));

                            fun copy row
                                =
                                {   (rw_vector::get (new_a, row)) ->   TEXT_LINE (new_ba, _);
                                    (rw_vector::get (old_a, row)) ->   TEXT_LINE (old_ba, old_hl);

                                    fun cpy col
                                        =
                                        {   rw_vector_of_chars::set (new_ba, col, rw_vector_of_chars::get (old_ba, col));
                                            cpy (col+1);
                                        };

                                    fun clip_hl ([], l)
                                            =>
                                            reverse_and_prepend (l, []);

                                        clip_hl ((c, n) ! r, l)
                                            =>
                                            if (c >= cols)
                                                #
                                                reverse_and_prepend (l, []);

                                            elif (c+n <= cols)
                                                #
                                                clip_hl (r, (c, n) ! l);
                                            else
                                                reverse_and_prepend (l, [(c, cols-c)]);
                                            fi;
                                    end;

                                    rw_vector::set (new_a, row, TEXT_LINE (new_ba, clip_hl (old_hl, [])));

                                    (cpy 0)
                                    except
                                        _ = ();

                                    copy (row+1);
                                };

                                (copy 0) except _ => ();

                            end;
                        end;            # fun resize_text_buf 

                end;                    # reverse_and_prepend stipulate
            end;                        # Text_Buf stipulate (abstype replacement recipie).
        end;                            # Text_Line stipulate


        # *** The text window ***
        # This is a dumb text window that
        # supports drawing text in normal
        # and highlighted mode:
        #
        stipulate
            Text_Window                                                                                 # Start of abstype-replacement recipe -- see http://successor-ml.org/index.php?title=Degrade_abstype_to_derived_form 
                =                                                                                       #
                TEXT_WINDOW  {                                                                          #
                                                                                                        #
                  root_window:    wg::Root_Window,                                                      #
                                                                                                        #
                  window:  xc::Window,                                                                  #
                  font:    xc::Font,                                                                    #
                                                                                                        #
                  rows:  Int,                                                                           #
                  cols:  Int,                                                                           #
                                                                                                        #
                  char_high:  Int,                                                                      #
                  char_wide:  Int,                                                                      #
                  char_ascent:  Int,                                                                    #
                                                                                                        #
                  draw_text:       { col:  Int, row:  Int, s:  String } -> Void,                        #
                  highlight_text:  { col:  Int, row:  Int, s:  String } -> Void,                        #
                  stipple:         { col:  Int, row:  Int, highlight:  Bool } -> Void,                  #
                  clear_line:      { row:  Int, start_col:  Int, end_col:  Int } -> Void,               #
                  clear_blk:       { start_row:  Int, end_row:  Int } -> Void,                          #
                                                                                                        #
                  char_blt                                                                              #
                      :                                                                                 #
                      { row:    Int,                                                                    #
                        from:   Int,                                                                    #
                        to:     Int,                                                                    #
                        nchars: Int                                                                     #
                      }                                                                                 #
                      ->                                                                                #
                      Mailop( List( g2d::Box ) ),                                                       #
                                                                                                        #
                  line_blt:  { from:  Int, to:  Int, nlines:  Int } -> Mailop( List( g2d::Box ) )       #
                };                                                                                      #
        herein /* Text_Window */                                                                                                #
            Text_Window = Text_Window;                                                                  # End of abstype-replacement recipe.

            stipulate /* char_blt */

                # Blt a block of text within a line 
                #
                fun char_blt (window, TEXT_SIZE { char_high, char_wide, size=>{ wide, ... }, ... } )
                    =
                    blt
                    where
                        pixel_blt
                            =
                            xc::pixel_blt_mailop
                                (xc::drawable_of_window  window)
                                xc::default_pen;


                        fun blt { row, from, to, nchars }
                            =
                            {   yyy = (char_high * row) + pad;
                                #
                                pixel_blt
                                  {
                                    from => xc::FROM_WINDOW window,
                                    #
                                    to_pos
                                        =>
                                        { col => pad + to*char_wide,
                                          row => yyy
                                        },

                                    from_box
                                        =>
                                        { col  =>  pad + from*char_wide,
                                          row  =>  yyy,
                                          wide =>  nchars*char_wide,
                                          high =>  char_high
                                        }
                                  };
                            };
                    end;                        # fun char_blt 

                # Blt a block of text by lines:
                #
                fun line_blt (window, TEXT_SIZE { char_high, char_wide, size=>{ wide, ... }, ... } )
                    =
                    blt
                    where
                        pixel_blt
                            =
                            xc::pixel_blt_mailop
                                (xc::drawable_of_window  window)
                                xc::default_pen;

                        text_wide = wide - tot_pad;

                        fun blt { from, to, nlines }
                            =
                            {   from_y =  (char_high * from) + pad;
                                to_y   =  (char_high * to  ) + pad;

                                pixel_blt
                                  {
                                    from   =>  xc::FROM_WINDOW window,
                                    to_pos =>  { col=>pad, row=>to_y },

                                    from_box
                                        =>
                                        { col  =>  pad,
                                          row  =>  from_y,
                                          wide =>  text_wide,
                                          high =>  (char_high * nlines)
                                        }
                                  };
                            };
                    end;                        # fun line_blt 

                # A stipple pattern for the cursor:
                #
                cursor_stipple_data
                    =
                    (16, [[
                        "0x8888", "0x2222", "0x1111", "0x4444",
                        "0x8888", "0x2222", "0x1111", "0x4444",
                        "0x8888", "0x2222", "0x1111", "0x4444",
                        "0x8888", "0x2222", "0x1111", "0x4444"
                      ]]);

            herein /*  char_blt */ 

                # Make a text window of the given size:
                #       
                fun make_text_window (root_window, window, font, size)
                    =
                    {   size ->  TEXT_SIZE { size=>{ wide, high }, rows, cols, char_high, char_wide, ascent };
                          #
                        my (pen, highlighter, normal_stipple, highlight_stipple)
                            =
                            {
                                black   =  xc::black;
                                white   =  xc::white;

                                stipple =  wg::ro_pixmap root_window "lightGray";

                                ( xc::make_pen [xc::p::FOREGROUND (xc::rgb8_from_rgb black), xc::p::BACKGROUND (xc::rgb8_from_rgb  white)],
                                  xc::make_pen [xc::p::FOREGROUND (xc::rgb8_from_rgb white), xc::p::BACKGROUND (xc::rgb8_from_rgb  black)],
                                  #
                                  xc::make_pen [xc::p::FOREGROUND (xc::rgb8_from_rgb black), xc::p::FILL_STYLE_STIPPLED, xc::p::STIPPLE stipple],
                                  xc::make_pen [xc::p::FOREGROUND (xc::rgb8_from_rgb white), xc::p::FILL_STYLE_STIPPLED, xc::p::STIPPLE stipple]
                                );
                              };

                        fun cc_to_pt { row, col }
                            =
                            { x => (col * char_wide) + pad,
                              y => (row * char_high)  + pad
                            };


                        fun draw_text (clear, draw) { row, col, s }
                            =
                            {   my { x, y } = cc_to_pt { row, col };

                                clear (  { col=>x, row=>y, wide=>char_wide*(string::length_in_bytes s), high=>char_high } );
                                draw  ({ col=>x, row=>y+ascent }, s);
                            };


                        fun stipple { row, col, highlight }
                            =
                            {   my { x, y }
                                    =
                                    cc_to_pt { row, col };

                                box = ({ col=>x, row=>y, wide=>char_wide, high=>char_high } );

                                highlight   ??   xc::fill_box (xc::drawable_of_window window) highlight_stipple  box
                                            ::   xc::fill_box (xc::drawable_of_window window)    normal_stipple  box;
                            };


                        clr_box
                            =
                            xc::clear_box  (xc::drawable_of_window  window);


                        fun clear_ln { row, start_col, end_col }
                            =
                            {   my { x, y }
                                    =
                                    cc_to_pt { row, col=>start_col };

                                clr_box ({ col=>x, row=>y, wide=>(end_col-start_col)*char_wide, high=>char_high } );
                            };

                        fun clear_blk { start_row, end_row }
                            =
                            {
                                my { x, y }
                                    =
                                    cc_to_pt { row=>start_row, col=>0 };

                                clr_box ({ col=>x, row=>y, wide=>(wide-tot_pad), high=>(end_row - start_row)*char_high } );
                            };

                          TEXT_WINDOW {
                              root_window,
                              window,
                              font,
                              rows, cols,
                              char_high,
                              char_wide,
                              char_ascent => ascent,

                              draw_text
                                  =>
                                  draw_text
                                    ( clr_box,
                                      #
                                      xc::draw_transparent_string
                                          #
                                          (xc::drawable_of_window  window)
                                          pen
                                          font
                                    ),

                              highlight_text
                                  =>
                                  draw_text
                                    ( xc::fill_box (xc::drawable_of_window window) pen,
                                      xc::draw_transparent_string (xc::drawable_of_window window) highlighter font
                                    ),

                              stipple,
                              clear_line => clear_ln,
                              clear_blk,
                              char_blt => char_blt (window, size),
                              line_blt => line_blt (window, size)
                            };
                      };                                # fun make_text_window

                # Create a new text window descriptor
                # to reflect a change in the window size:
                #
                fun resize_text_window (TEXT_WINDOW { window, font, root_window, ... }, new_size)
                    =
                    make_text_window (root_window, window, font, new_size);

                # Draw a string in normal mode
                # at the given position:
                #
                fun draw_ntext { window=>TEXT_WINDOW { draw_text, ... }, row, col, text }
                    =
                    draw_text { row, col, s=>text };

                # Draw a string in highlight mode
                # at the given position:
                #
                fun draw_htext { window=>TEXT_WINDOW { highlight_text, ... }, row, col, text }
                    =
                    highlight_text { row, col, s=>text };

                # Stipple a normal mode character position:
                #
                fun stipple_nchar { window=>TEXT_WINDOW { stipple, ... }, row, col }
                    =
                    stipple { row, col, highlight=>FALSE };

                # Stipple a highlight mode character position:
                #
                fun stipple_hchar { window=>TEXT_WINDOW { stipple, ... }, row, col }
                    =
                    stipple { row, col, highlight=>TRUE };

                # Clear a character:
                # 
                fun clear_window_char (TEXT_WINDOW { clear_line, ... }, CHAR_POINT { row, col } )
                    =
                    clear_line { row, start_col => col, end_col => col+1 };

                # Clear from a character position
                # to the end of the line:
                #
                fun clear_window_ln (TEXT_WINDOW { clear_line, cols, ... }, CHAR_POINT { row, col } )
                    =
                    clear_line { row, start_col => col, end_col => cols };

                # Clear from a row to the end
                # of the screen:
                #
                fun clear_window { window => TEXT_WINDOW { clear_blk, ... }, from, to }
                    =
                    clear_blk { start_row => from, end_row => to };

                # Delete characters;
                #
                fun delete_window_chars (TEXT_WINDOW { clear_line, char_blt, cols, ... }, CHAR_POINT { row, col }, count)
                    =
                    {   eolcnt = cols - col - count;

                        if (eolcnt > 0)
                              mailop = char_blt { row, from=>col+count, to=>col, nchars=>eolcnt };

                              clear_line { row, start_col => cols-count, end_col => cols };

                              block_until_mailop_fires  mailop;
                        else
                            clear_line { row, start_col => col, end_col => cols };
                            [];
                        fi;
                    };

                # Insert text:
                #
                fun insert_window_text (tw, CHAR_POINT { row, col }, str, highlight)
                    =
                    {   tw ->  TEXT_WINDOW { draw_text, highlight_text, char_blt, cols, ... };

                        count = size str;

                        txtfn = if highlight  highlight_text;
                                else          draw_text;
                                fi;

                        eolcnt = cols - col - count;

                        if (eolcnt <= 0)
                             #
                             txtfn { row, col, s=>str };
                             [];
                        else
                             mailop =  char_blt { row, from=>col, to=>col+count, nchars=>eolcnt };

                             txtfn { row, col, s=>str };

                             block_until_mailop_fires  mailop;
                        fi;
                    };

                # Scroll a region of text up; "from" is the bottom line of the text, "to"
                # is where "from" is move to, and "nlines" is the size of the block.
                #
                fun scroll_window_up { window=>TEXT_WINDOW { line_blt, clear_blk, ... }, from, to, nlines=>0 }
                        =>
                        {   clear_blk { start_row=>to+1, end_row=>from+1 };
                            [];
                        };

                    scroll_window_up { window=>TEXT_WINDOW { line_blt, clear_blk, ... }, from, to, nlines }
                        =>
                        {   mailop =  line_blt  { from=>from-to, to=>0, nlines };
                            #
                            clear_blk { start_row=>to+1, end_row=>from+1 };

                            block_until_mailop_fires  mailop;
                        };
                end;

                # Scroll a region of text down; "from" is the top line of the text, "to"
                # is where "from" is moved to, and "nlines" is the size of the block.
                #       
                fun scroll_window_down { window=>TEXT_WINDOW { clear_blk, ... }, from, to, nlines=>0 }
                        =>
                        {   clear_blk { start_row=>from, end_row=>to };
                            [];
                        };

                    scroll_window_down { window=>TEXT_WINDOW { line_blt, clear_blk, ... }, from, to, nlines }
                        =>
                        {   mailop =  line_blt  { from, to, nlines };
                            #
                            clear_blk { start_row=>from, end_row=>to };

                            block_until_mailop_fires  mailop;
                        };
                end;

                # Delete a region of text; "from" is the start of the block, "nlines" is the
                # number of lines to delete.  The text below the delete block is scrolled up
                # to fill the space, with blank lines filling from the bottom.
                #
                fun delete_window_lines { window=>TEXT_WINDOW { rows, clear_blk, ... }, from, to, nlines=>0 }
                        =>
                        {   clear_blk { start_row=>from, end_row=>rows };
                            [];
                        };

                   delete_window_lines { window=>TEXT_WINDOW { rows, line_blt, clear_blk, ... }, from, to, nlines }
                       =>
                       {   mailop = line_blt { from=>to, to=>from, nlines };
                           #
                           clear_blk { start_row=>from+nlines, end_row=>rows };

                           block_until_mailop_fires  mailop;
                       };
                end;

            end;        #  char_blt stipulate
        end;            #  text_window stipulate (abstype replacement)


        # *** The internal text widget state ***
        # The internal state of the text widget consists of the current size, a text
        # buffer, a text window and a cursor.
        #
        Text = TEXT { size:        Text_Size,
                      txt_buf:     Text_Buf,
                      txt_window:  Text_Window,
                      cursor:  { is_on:  Bool, pos:  Char_Point }
                    };

        # Draw the cursor 
        #
        fun draw_cursor (TEXT { txt_buf, txt_window, cursor=> { pos=>CHAR_POINT { row, col }, ... }, ... } )
            =
            case (explode_row { text=>txt_buf, row, col, len=>1 } )

                  ("" ! _ ) =>  stipple_hchar { window=>txt_window, col, row };
                  _         =>  stipple_nchar { window=>txt_window, col, row };
            esac;


        # Erase the cursor:
        #
        fun erase_cursor (TEXT { txt_buf, txt_window, cursor=> { pos=>CHAR_POINT { row, col }, ... }, ... } )
            =
            case (explode_row { text=>txt_buf, row, col, len=>1 } )

               []           =>  clear_window_char (txt_window, CHAR_POINT { row, col } );
               ("" ! s ! _) =>  draw_htext { window=>txt_window, col, row, text=>s };
               (" " ! _)    =>  clear_window_char (txt_window, CHAR_POINT { row, col } );
               (s ! _)      =>  draw_ntext { window=>txt_window, col, row, text=>s };
            esac;


        # Redraw damaged lines (but not the cursor):
        #
        fun redraw_text (TEXT { size, txt_buf, txt_window, ... }, damage)
            =
            {   size ->  TEXT_SIZE { rows, cols, char_high, char_wide, ... };
                #
                damage_vec = rw_vector::make_rw_vector (rows, NULL);


                fun mark (i, min_col, max_col)
                    =
                    case (rw_vector::get (damage_vec, i))
                        #
                        THE (a, b)
                            =>
                            rw_vector::set (damage_vec, i, THE (min (min_col, a), max (max_col, b)));

                        NULL
                            =>
                            rw_vector::set (damage_vec, i, THE (min_col, max_col));
                    esac;


                fun mark_damage []
                        =>
                        ();

                    mark_damage ({ col=>x, row=>y, wide, high } ! r)
                        =>
                        {   top_ln = int::quot (y - pad, char_high);
                            bot_ln = int::min (int::quot((y - pad) + high + (char_high  - 1), char_high), rows);

                            min_c  = int::quot (x - pad, char_wide);
                            max_c  = int::min (int::quot((x - pad) + wide + (char_wide - 1), char_wide), cols);

                            fun f i
                                =
                                if (i < bot_ln)
                                    #
                                    mark (i, min_c, max_c);
                                    f (i+1);
                                fi;

                              f top_ln;
                              mark_damage r;
                        };
                end;


                fun redraw_damaged_lines row
                    =
                    {   case (rw_vector::get (damage_vec, row))
                            #
                            NULL => ();

                            THE (min_col, max_col)
                                =>
                                {
                                    strs = explode_row {
                                            text=>txt_buf, row, col=>min_col, len=>max_col-min_col };

                                    fun draw_n (_, []) => ();
                                        draw_n (i, "" ! r) => draw_h (i, r);

                                        draw_n (i, s ! r)
                                            =>
                                            {   draw_ntext { window=>txt_window, row, col=>i, text=>s };
                                                draw_h (i + string::length_in_bytes s, r);
                                            };
                                    end 

                                    also
                                    fun draw_h (_, [])
                                            =>
                                            ();

                                        draw_h (i, s ! r)
                                            =>
                                            {   draw_htext { window=>txt_window, row, col=>i, text=>s };
                                                draw_n (i + string::length_in_bytes s, r);
                                            };
                                    end;

                                    draw_n (min_col, strs);
                               };
                        esac;

                        redraw_damaged_lines (row+1);
                    };

                #  file::print "redraw start\n"; 

                mark_damage damage;

                (redraw_damaged_lines 0)
                except
                    _ = ();

                #  file::print "redraw done\n";
            };

        # Redraw (including the cursor) 
        #
        fun redraw (txt as TEXT { cursor, ... }, damage)
            =
            {   redraw_text (txt, damage);

                case cursor
                    #
                    { is_on=>TRUE, pos } =>  draw_cursor txt;
                    _                    =>  ();
                esac;
            };


        # Complete a area operation by redrawing any missing rectangles:
        #
        fun repair (_,   []) =>  ();
            repair (txt, rl) =>  redraw_text (txt, rl);
        end;


        # Resize the text buffer and text window:
        #
        fun resize (TEXT { txt_buf, txt_window, ... }, font, { wide, high, ... }: g2d::Box)
            =
            {   new_size = make_text_size ({ wide, high }, font);

                # * DO WE NEED TO REFRESH?? *
                #  file::print "resize start\n"; 

                TEXT {
                    txt_buf => resize_text_buf (txt_buf, new_size),
                    txt_window => resize_text_window (txt_window, new_size),
                    size => new_size,
                    cursor => { is_on => FALSE, pos => CHAR_POINT { row=>0, col=>0 }}
                  };
            };


        # Return the size info of the widget state:
        #
        fun get_info (TEXT { size, ... } )
            =
            size;

        # Return the cursor info of the widget state:
        #
        fun get_cursor_info (TEXT { cursor, ... } )
            =
            cursor;

        # Scroll the text from line "from" up "n" lines:
        #
        fun scroll_up (txt, from, n)
            =
            {   txt ->   TEXT { size=>TEXT_SIZE { rows, ... }, txt_buf, txt_window, cursor };
                #
                to     =  from - n;
                blk_size =  to + 1;

                interfere = case cursor
                                #
                                { is_on=>TRUE, pos => CHAR_POINT { row, ... } }
                                    =>
                                    (from >= row);

                                _ => FALSE;
                            esac;

                if (n > 0 and -1 <= to and from < rows)
                    #
                    if interfere
                        #
                        erase_cursor txt;
                        move_text_up { text=>txt_buf, from, to, nlines=>blk_size };
                        repair (txt, scroll_window_up { window=>txt_window, from, to, nlines=>blk_size } );
                        draw_cursor txt;
                    else
                        move_text_up { text=>txt_buf, from, to, nlines=>blk_size };
                        repair (txt, scroll_window_up { window=>txt_window, from, to, nlines=>blk_size } );
                    fi;
                fi;
              };                        # fun scroll_up 

        # Scroll the text starting at line "from" down "n" lines. 
        #
        fun scroll_down (txt, from, n)
            =
            {   txt ->  TEXT { size=>TEXT_SIZE { rows, ... }, txt_buf, txt_window, cursor };
                #
                to = from + n;
                blk_size = rows - to;

                interfere
                    =
                    case cursor
                        { is_on=>TRUE, pos => CHAR_POINT { row, ... } } =>  from <= row;
                        _                                               =>  FALSE;
                    esac;

                if (n > 0  and  0 <= from  and  to <= rows)
                    #
                    if interfere
                        #
                        erase_cursor txt;
                        move_text_down { text=>txt_buf, from, to, nlines=>blk_size };
                        repair (txt, scroll_window_down { window=>txt_window, from, to, nlines=>blk_size } );
                        draw_cursor txt;
                    else
                        move_text_down { text=>txt_buf, from, to, nlines=>blk_size };
                        repair (txt, scroll_window_down { window=>txt_window, from, to, nlines=>blk_size } ); 
                    fi;
                fi;
            };                                          # fun scroll_down 

        # Delete "nlines" starting from "from" 
        #
        fun delete_lines (txt, from, nlines)
            =
            {   txt ->  TEXT { size=>TEXT_SIZE { rows, ... }, txt_buf, txt_window, cursor };
                #
                to     =  from + nlines;
                blk_size =  rows - to;

                interfere
                    =
                    case cursor
                        #
                        { is_on=>TRUE, pos => CHAR_POINT { row, ... } }
                            =>
                            from <= row;

                         _  =>
                            FALSE;
                    esac;

                if (nlines > 0  and  0 <= from  and  to <= rows)
                    #
                    if interfere
                        #
                        erase_cursor txt;
                        delete_text { text=>txt_buf, from, nlines };
                        repair (txt, delete_window_lines { window=>txt_window, from, to, nlines=>blk_size } );
                        draw_cursor txt;
                    else
                        delete_text { text=>txt_buf, from, nlines };
                        repair (txt, delete_window_lines { window=>txt_window, from, to, nlines=>blk_size } );
                    fi;
                  fi;
              };

        # Clear from "pos" to the end of the line 
        #
        fun clear_eol (txt, pos as CHAR_POINT { row, col } )
            =
            {   txt ->   TEXT { size, txt_buf, txt_window, cursor };

                interfere
                    =
                    case cursor
                        #
                        { is_on=>TRUE, pos=>CHAR_POINT { row=>cr, col=>cc } }
                            =>
                            cr == row  and  col <= cc;

                         _ => FALSE;
                    esac;

                if (in_text_window (size, pos))
                    #
                    clear_text_ln (txt_buf, pos);
                    clear_window_ln  (txt_window, pos);

                    interfere  ?:   draw_cursor txt;
                fi;
            };

        # Clear from "pos" to the end of the screen:
        #
        fun clear_eos (txt, pos as CHAR_POINT { row, col } )
            =
            {   my (pos as CHAR_POINT { row, ... } )
                    =
                    col != 0   ??   { clear_eol (txt, pos); CHAR_POINT { row=>row+1, col=>0 }; }
                               ::   pos;

                txt ->   TEXT { size as TEXT_SIZE { rows, ... }, txt_buf, txt_window, cursor };

                interfere
                    =
                    case cursor
                        #
                        { is_on=>TRUE, pos => CHAR_POINT { row=>cr, ... } }
                            =>
                            row <= cr;

                        _ => FALSE;
                    esac;

                if (in_text_window (size, pos))
                    #
                    clear_text { text => txt_buf, from => row, to => rows };
                    clear_window  { window  => txt_window, from => row, to => rows };

                    interfere   ?:   draw_cursor txt;
                fi;
              };

        #  Will text drawing interfere with cursor? 
        #
        fun fix_cursor (TEXT { cursor=> { is_on=>FALSE, ... }, ... }, _, _)
                =>
                ();

            fix_cursor (txt, CHAR_POINT { row, col }, str)
                =>
                {   txt ->   TEXT { cursor=> { pos=>CHAR_POINT { row=>cr, col=>cc }, ... }, ... };
                    #
                    if  (cr == row
                    and  cc >= col
                    and  cc <  col + string::length_in_bytes str
                    )
                         draw_cursor txt;
                    fi;
                };
        end;

        #  Draw "str" at "pos" in normal mode 
        #
        fun write_string (txt, pos as CHAR_POINT { row, col }, str)
            =
            {   txt -> TEXT { size, txt_buf, txt_window, ... };
                #
                if (in_text_window (size, pos))
                    #
                    str = clip_string (size, col, str);

                    write_ntext (txt_buf, row, col, str);
                    draw_ntext { window=>txt_window, row, col, text=>str };
                    fix_cursor (txt, pos, str);
                fi;
            };

        #  Draw "str" at "pos" in highlighted mode 
        #
        fun highlight_string (txt, pos as CHAR_POINT { row, col }, str)
            =
            {   txt ->   TEXT { size, txt_buf, txt_window, ... };
                #
                if (in_text_window (size, pos))
                    #
                    str = clip_string (size, col, str);

                    write_htext (txt_buf, row, col, str);
                    draw_htext { window=>txt_window, row, col, text=>str };
                    fix_cursor (txt, pos, str);
                fi;
            };

        # Insert text at pos.
        #
        fun insert_text (txt, pos as CHAR_POINT { row, col }, str, highlight)
            =
            {   txt ->   TEXT { size, txt_buf, txt_window, cursor };
                #
                interfere
                    =
                    case cursor
                        #
                        { is_on=>TRUE, pos=>CHAR_POINT { row=>cr, col=>cc } }
                            =>
                            (cr == row) and (col <= cc);

                        _ => FALSE;
                    esac;

                if (in_text_window (size, pos))
                    #
                    str = clip_string (size, col, str);

                    insert_buf_text (txt_buf, row, col, str, highlight);
                    repair (txt, insert_window_text (txt_window, pos, str, highlight));

                    interfere   ?:   draw_cursor txt;

                fi;
            };

        # Delete count characters at position pos.
        # Fill with spaces on right.
        # Assume count > 0.
        #
        fun delete_chars (txt, pos as CHAR_POINT { row, col }, count)
            =
            {   txt ->   TEXT { size, txt_buf, txt_window, cursor };
                #
                interfere
                    =
                    case cursor
                        #
                        { is_on=>TRUE, pos=>CHAR_POINT { row=>cr, col=>cc } }
                            =>
                            cr == row  and  col <= cc;

                        _ => FALSE;
                    esac;

                if (in_text_window (size, pos))
                    #
                    delete_text_chars (txt_buf, row, col, count);
                    repair (txt, delete_window_chars (txt_window, pos, count));

                    interfere   ?:   draw_cursor txt;
                fi;
            };

        fun move_cursor (txt, new_pos)
            =
            {   txt ->   TEXT { size, txt_buf, txt_window, cursor as { is_on, pos } };
                #
                if (in_text_window (size, new_pos) and (pos != new_pos))
                    #
                    new_txt = TEXT { size, txt_buf, txt_window, cursor => { is_on, pos=>new_pos } };

                    if is_on
                         erase_cursor txt;
                         draw_cursor new_txt;
                    fi;

                    new_txt;
                else
                    txt;
                fi;
            };

        fun set_cursor (txt as TEXT { size, txt_buf, txt_window, cursor => { is_on, pos }}, on)
            =
            {   new_txt = TEXT { size, txt_buf, txt_window, cursor => { is_on => on, pos } };
                #
                case (is_on, on)
                   ( TRUE, FALSE) =>  erase_cursor txt;
                   (FALSE, TRUE ) =>  draw_cursor new_txt;
                   _              =>  ();
                esac;

                new_txt;
            };


        # ** The text widget ***
        # The text widget is represented by a plea/reply pair of communication
        # channels.
        #
        Plea_Mail
          = GET_INFO
          | GET_CURSOR_INFO
          | SCROLL_UP  { from:  Int, nlines:  Int }
          | SCROLL_DOWN  { from:  Int, nlines:  Int }
          | DELETE_LINES  { lnum:  Int, nlines:  Int }
          | CLEAR_LINE  Char_Point
          | CLEAR_SCR  Char_Point
          | WRITE_STRING  { pos:  Char_Point, str:  String }
          | HIGHLIGHT_STRING  { pos:  Char_Point, str:  String }
          | INSERT_TEXT  { pos:  Char_Point, str:  String, highlight:  Bool }
          | DELETE_CHARS  { pos:  Char_Point, count:  Int }
          | MOVE_CURSOR  Char_Point
          | SET_CURSOR  Bool
          ;

        Reply_Mail
          = INFO  Text_Size
          | CURSOR_INFO  { is_on:  Bool, pos:  Char_Point }
          ;

        Text_Widget
            =
            TEXT_WIDGET
              {
                widget:  wg::Widget,
                query:   Plea_Mail -> Reply_Mail,
                cmd:     Plea_Mail -> Void
              };

        # Create a new text widget:
        #
        fun make_text_widget
            root_window
            { rows:  Int,
              cols:  Int
            }
            =
            {   rows = max (rows, 1);
                cols = max (cols, 1);

                plea_slot =  make_mailslot ();
                reply_slot   =  make_mailslot ();

                plea' =  take_from_mailslot'  plea_slot;

                font  =  xc::find_else_open_font  (wg::xsession_of root_window)  font_name;

                (font_info font)
                    ->
                    (char_high, char_wide, _);

                fun realize_widget { kidplug, window, window_size }
                    =
                    {   (xc::ignore_mouse_and_keyboard  kidplug)
                            ->
                            xc::KIDPLUG { from_other', to_mom, ... };

                        tsz = make_text_size (window_size, font);

                        text = TEXT {
                                size => tsz,
                                txt_buf => make_text_buf tsz,
                                txt_window => make_text_window (root_window, window, font, tsz),
                                cursor => { is_on => FALSE, pos=>CHAR_POINT { row=>0, col=>0 }}
                              };

                        fun imp_loop  text
                            =
                            {
                                fun do_other  envelope
                                    =
                                    case (xc::get_contents_of_envelope  envelope)
                                        #
                                        xc::ETC_REDRAW damage
                                            =>
                                            {   redraw (text, damage);
                                                imp_loop text;
                                            };

                                        xc::ETC_RESIZE new_r
                                            =>
                                            imp_loop (resize (text, font, new_r));

                                        xc::ETC_OWN_DEATH
                                            =>
                                            thread_exit { success => TRUE };

                                       _ => impossible("macroExpand",
                                            "[text_widget: unexpected CI message]");
                                    esac;

                                fun do_plea (GET_INFO)
                                        =>
                                        {   put_in_mailslot (reply_slot, INFO (get_info text));
                                            imp_loop text;
                                        };

                                    do_plea (GET_CURSOR_INFO)
                                        =>
                                        {   put_in_mailslot (reply_slot, CURSOR_INFO (get_cursor_info text));
                                            imp_loop text;
                                        };

                                    do_plea (SCROLL_UP { from, nlines } )
                                        =>
                                        {   scroll_up (text, from, nlines);
                                            imp_loop text;
                                        };

                                    do_plea (SCROLL_DOWN { from, nlines } )
                                        =>
                                        {   scroll_down (text, from, nlines);
                                            imp_loop text;
                                        };

                                    do_plea (DELETE_LINES { lnum, nlines } )
                                        =>
                                        {   delete_lines (text, lnum, nlines);
                                            imp_loop text;
                                        };

                                    do_plea (CLEAR_LINE cc)
                                        =>
                                        {   clear_eol (text, cc);
                                            imp_loop text;
                                        };

                                    do_plea (CLEAR_SCR cc)
                                        =>
                                        {   clear_eos (text, cc);
                                            imp_loop text;
                                        };

                                    do_plea (HIGHLIGHT_STRING { pos, str } )
                                        =>
                                        {   highlight_string (text, pos, str);
                                            imp_loop text;
                                        };

                                    do_plea (WRITE_STRING { pos, str } )
                                        =>
                                        {   write_string (text, pos, str);
                                            imp_loop text;
                                        };

                                    do_plea (INSERT_TEXT { pos, str, highlight } )
                                        =>
                                        {   insert_text (text, pos, str, highlight);
                                            imp_loop text;
                                        };

                                    do_plea (DELETE_CHARS { pos, count } )
                                        =>
                                        {   delete_chars (text, pos, count);
                                            imp_loop text;
                                        };

                                    do_plea (MOVE_CURSOR cc)
                                        =>
                                        imp_loop (move_cursor (text, cc));

                                    do_plea (SET_CURSOR on)
                                        =>
                                        imp_loop (set_cursor (text, on));
                                end;

                                block_until_mailop_fires (
                                    cat_mailops
                                      [
                                        from_other' ==>  do_other,
                                        plea'       ==>  do_plea
                                      ]
                                );
                            };

                          xlogger::make_thread  "text_widget_imp"  {.
                              #
                              imp_loop  text;
                          };

                          ();
                      };

                  TEXT_WIDGET
                    {
                      query  => (\\ plea = {  put_in_mailslot (plea_slot, plea);   take_from_mailslot reply_slot;  }),
                      cmd    => (\\ plea =    put_in_mailslot (plea_slot, plea)),

                      widget => wg::make_widget
                                  {
                                    root_window,
                                    args => \\ () = { background => NULL }, 

                                    size_preference_thunk_of
                                        =>
                                        \\ () = { col_preference =>  wg::INT_PREFERENCE { start_at=>tot_pad, step_by=>char_wide, min_steps=>1, best_steps=>cols, max_steps=>NULL },
                                                  row_preference =>  wg::INT_PREFERENCE { start_at=>tot_pad, step_by=>char_high,  min_steps=>1, best_steps=>rows, max_steps=>NULL }
                                                },

                                    realize_widget
                                  }
                    };
            };                          # fun make_text_widget 

        fun as_widget (TEXT_WIDGET { widget, ... } )
            =
            widget;

        fun get_info (TEXT_WIDGET { query, ... } )
            =
            case (query GET_INFO)
                #
                INFO info => info;
                _         => impossible ("getInfo", "[]");
            esac;

        fun char_size_of tw
            =
            {   (get_info tw)
                    ->
                    TEXT_SIZE { rows, cols, ... };

                { rows, cols };
            };

        fun size_of tw
            =
            {   (get_info tw)
                    ->
                    TEXT_SIZE { size, ... };

                size;
            };

        fun point_to_coordinate tw point
            =
            {   (get_info tw)
                    ->
                    TEXT_SIZE { size, char_high, char_wide, ... };

                (g2d::point::clip (point, size))
                    ->
                    { col, row };

                CHAR_POINT {
                    row => int::quot (row - pad, char_high),
                    col => int::quot (col - pad, char_wide)
                  };
            };

        fun coordinate_to_box  tw  (CHAR_POINT { row, col } )
            =
            {   (get_info tw)
                    ->
                    TEXT_SIZE { char_wide, char_high, rows, cols, ... };

                row = if (row < 0)  0; elif (row < rows)  row; else (rows - 1);  fi;
                col = if (col < 0)  0; elif (col < cols)  col; else (cols - 1);  fi;

                { col  => (col*char_wide) + pad,
                  row  => (row*char_high) + pad,
                  #
                  wide => char_wide,
                  high => char_high
                };
            };

        fun scroll_up   (TEXT_WIDGET { cmd, ... } ) arg =  cmd (SCROLL_UP   arg);
        fun scroll_down (TEXT_WIDGET { cmd, ... } ) arg =  cmd (SCROLL_DOWN arg);


        fun write_text (TEXT_WIDGET { cmd, ... } ) { at, text }
            =
            cmd (WRITE_STRING { pos=>at, str=>text } );


        fun highlight_text (TEXT_WIDGET { cmd, ... } ) { at: Char_Point, text:  String }
            =
            cmd (HIGHLIGHT_STRING { pos=>at, str=>text } );


        fun insert_line (TEXT_WIDGET { cmd, ... } ) { lnum, text }
            =
            {   cmd (SCROLL_DOWN { from=>lnum, nlines=>1 } );
                cmd (WRITE_STRING { pos=>CHAR_POINT { row=>lnum, col=>0 }, str=>text } );
            };


        fun insert_text (TEXT_WIDGET { cmd, ... } ) { at, text => ""}
                =>
                ();

            insert_text (TEXT_WIDGET { cmd, ... } ) { at, text }
                =>
                cmd (INSERT_TEXT { pos=>at, str=>text, highlight=>FALSE } );
        end;


        fun insert_highlight_text (TEXT_WIDGET { cmd, ... } ) { at: Char_Point, text:  String }
            =
            cmd (INSERT_TEXT { pos=>at, str=>text, highlight=>TRUE } );

        fun delete_line  (TEXT_WIDGET { cmd, ... } ) lnum =  cmd (DELETE_LINES { lnum, nlines => 1 } );
        fun delete_lines (TEXT_WIDGET { cmd, ... } ) arg  =  cmd (DELETE_LINES arg);

        fun delete_chars (TEXT_WIDGET { cmd, ... } ) { at:  Char_Point, count:  Int }
            =
            if (count > 0)
                cmd (DELETE_CHARS { pos=>at, count } );
            fi;

        fun clear_to_eol (TEXT_WIDGET { cmd, ... } ) coord =  cmd (CLEAR_LINE coord);
        fun clear_to_eos (TEXT_WIDGET { cmd, ... } ) coord =  cmd (CLEAR_SCR coord);

        fun clear (TEXT_WIDGET { cmd, ... } )
            =
            cmd (CLEAR_SCR (CHAR_POINT { col=>0, row=>0 } ));

        fun get_cursor_info (TEXT_WIDGET { query, ... } )
            =
            case (query GET_CURSOR_INFO)
                #
                CURSOR_INFO info =>  info;
                _                =>  impossible ("getCursorInfo", "[]");
            esac;

        fun get_cursor_point tw
            =
            (get_cursor_info tw).pos;

        fun move_cursor (TEXT_WIDGET { cmd, ... } ) pos = cmd (MOVE_CURSOR pos);
        fun cursor_on   (TEXT_WIDGET { cmd, ... } )     = cmd (SET_CURSOR TRUE);
        fun cursor_off  (TEXT_WIDGET { cmd, ... } )     = cmd (SET_CURSOR FALSE);

    };                  # package text_widget 
end;


Comments and suggestions to: bugs@mythryl.org

PreviousUpNext