PreviousUpNext

15.4.1429  src/lib/x-kit/widget/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 


stipulate
    include threadkit;                          # threadkit     is from   src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg
    #
    package xg =  xgeometry;                    # xgeometry     is from   src/lib/std/2d/xgeometry.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/basic/widget.pkg
herein

    package text_widget: Text_Widget {          # Text_Widget   is from   src/lib/x-kit/widget/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_Coord
            =
            CHAR_COORD { 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
            =
            {   my { ascent, descent }
                    =
                    xc::font_high font;

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

        # A description of the various size parameters of a text window 
        #
        Text_Size
            =
            TEXT_SIZE  {
              size:       xg::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 xg::SIZE { wide, high }, font)
            =
            {   my (char_high, char_wide, ascent)
                    =
                    font_info font;

                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_COORD { 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 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))
                  );
        herein

            abstype Text_Buf
                =
                TEXT_BUF
                  { size:  xg::Size,
                    arr:   rw_vector::Rw_Vector( Text_Line )
                  }
            with
              stipulate

                  fun revappend ([],    l) =>  l;
                      revappend (x ! r, l) =>  revappend (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 (col, len, format)
                          =>
                          {   end_col = col+len;

                              fun prefix ([], _)
                                      =>
                                      format; #  text falls after highlight regions 

                                  prefix ((c, n) ! r, l)
                                      =>
                                      {   end_c = c+n;

                                          if (end_c <= col) 

                                               prefix (r, (c, n) ! l);

                                          elif (end_col <= c)

                                               format;          #  text falls between highlight regions 

                                          elif (c < col)

                                               if (end_c <= end_col)

                                                    suffix ((c, col-c) ! l, r);
                                               else
                                                    revappend (l, (c, col-c) ! (end_col, end_c-end_col) ! r);
                                               fi;

                                          elif (end_c <= end_col)

                                               suffix (l, r);   #  text covers (c, n) 
                                          else
                                               revappend (l, (end_col, end_c-end_col) ! r);
                                          fi;
                                      };
                              end  
                              also
                              fun suffix (pre, [])
                                      =>
                                      revappend (pre, []);

                                  suffix (pre, (c, n) ! r)
                                      =>
                                      {   end_c = c+n;

                                          if (end_c <= end_col)

                                               suffix (pre, r);

                                          elif (c < end_col)

                                               revappend (pre, (end_col, end_c-end_col) ! r);
                                          else
                                               revappend (pre, r);
                                          fi;
                                      };
                              end;

                              prefix (format, []);

                        };                      #  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) ) )
                          =>
                          [(col, len)];

                      ins_h (col, len, format)
                          =>
                          {
                              end_col = col+len;

                              fun prefix ([], l)
                                      =>
                                      revappend (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)

                                               revappend (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

                                               revappend (l, (col, end_c-col) ! r);

                                          fi;
                                    };
                              end 

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

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

                                         if (c > end_col)

                                              revappend (pre, (col, end_col-col) ! (c, n) ! r);

                                         elif (end_c < end_col)

                                              suffix (pre, col, end_col, r);
                                         else
                                              revappend (pre, (col, end_c-col) ! r);
                                         fi;
                                    };
                              end;

                              prefix (format, []);

                        };              # fun ins_h
                  end;

                  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 (fn (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 (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=>xg::SIZE { wide, ... }}, row, fromcol, tocol, nchars)
                      =
                      {   my TEXT_LINE (ba, format)
                              =
                              rw_vector::get (arr, row);

                          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

                  # 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=>xg::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)
                      =
                      {   my TEXT_LINE (ba, format)
                              =
                              rw_vector::get (arr, row);

                          write_string (ba, col, str);
                          rw_vector::set (arr, row, TEXT_LINE (ba, ins_n (col, string::length 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 str, format)));
                      };

                  # Insert a string into a text rw_vector, shifting chars to the right:
                  #
                  fun insert_buf_text (
                        tbuf as TEXT_BUF { size=>xg::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_COORD { row, col } )
                      =
                      {   my TEXT_LINE (ba, format) = rw_vector::get (arr, row);

                          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=>xg::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_COORD { row, col => wide-count } );
                          else clear_text_ln (tbuf, CHAR_COORD { row, col             } );
                          fi;
                      };

                  #  Clear the given block of text 
                  #
                  fun clear_text { text => TEXT_BUF { arr=>ar, ... }, from, to }
                      =
                      loop from
                      where
                          cols = {   my TEXT_LINE (ba, _) = rw_vector::get (ar, 0);
                                     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)
                                           =>
                                           revappend (l, [ext (i, end_col-i)]);

                                      suffix (i, (c, n) ! r, l)
                                          =>
                                          {    end_c = c+n;

                                               if (end_col <= c)

                                                    revappend (l, [ext (i, end_col-i)]);

                                               elif (end_c < end_col)

                                                    suffix (end_c, r, ext (c, n) ! ext (i, c-i) ! l);
                                               else
                                                    revappend (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
                          my (new_tb as (TEXT_BUF { arr=>new_a, ... } )) = make_text_buf new_size;

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

                                  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)
                                          =>
                                          revappend (l, []);

                                      clip_hl ((c, n) ! r, l)
                                          =>
                                          if (c >= cols)

                                               revappend (l, []);

                                          elif (c+n <= cols)

                                               clip_hl (r, (c, n) ! l);
                                          else
                                               revappend (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;                      # stipulate
            end;                        # Abstype text_buf 
        end;                    # stipulate


        # *** The text window ***
        # This is a dumb text window that
        # supports drawing text in normal
        # and highlighted mode:
        #
        abstype Text_Window
            =
            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( xg::Box ) ),

              line_blt:  { from:  Int, to:  Int, nlines:  Int } -> Mailop( List( xg::Box ) )
            }
        with

            stipulate

                # Blt a block of text within a line 
                #
                fun char_blt (window, TEXT_SIZE { char_high, char_wide, size=>xg::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
                                        =>
                                        xg::POINT
                                          { col => pad + to*char_wide,
                                            row => yyy
                                          },

                                    from_box
                                        =>
                                        xg::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=>xg::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 =>  xg::POINT { col=>pad, row=>to_y },

                                    from_box
                                        =>
                                        xg::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

              # Make a text window of the given size:
              # 
              fun make_text_window (root_window, window, font, size)
                  =
                  {   size ->  TEXT_SIZE { size=>xg::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 (xg::BOX   { col=>x, row=>y, wide=>char_wide*(string::length s), high=>char_high } );
                              draw  (xg::POINT { col=>x, row=>y+ascent }, s);
                          };


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

                              box = (xg::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 (xg::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 (xg::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_COORD { 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_COORD { 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_COORD { 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_COORD { 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; #  local 
        end; #  Abstype text_window 


        # *** 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_Coord }
                    };

        # Draw the cursor 
        #
        fun draw_cursor (TEXT { txt_buf, txt_window, cursor=> { pos=>CHAR_COORD { 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_COORD { row, col }, ... }, ... } )
            =
            case (explode_row { text=>txt_buf, row, col, len=>1 } )

               []           =>  clear_window_char (txt_window, CHAR_COORD { row, col } );
               ("" ! s ! _) =>  draw_htext { window=>txt_window, col, row, text=>s };
               (" " ! _)    =>  clear_window_char (txt_window, CHAR_COORD { 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 (xg::BOX { 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 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 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, xg::BOX { wide, high, ... } )
            =
            {   new_size = make_text_size (xg::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_COORD { 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_COORD { 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_COORD { 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_COORD { 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_COORD { row, col } )
            =
            {   txt ->   TEXT { size, txt_buf, txt_window, cursor };

                interfere
                    =
                    case cursor

                        { is_on=>TRUE, pos=>CHAR_COORD { 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_COORD { row, col } )
            =
            {
                my (pos as CHAR_COORD { row, ... } )
                    =
                    col != 0   ??   { clear_eol (txt, pos); CHAR_COORD { 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_COORD { 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_COORD { row, col }, str)
                =>
                {   txt ->   TEXT { cursor=> { pos=>CHAR_COORD { row=>cr, col=>cc }, ... }, ... };

                    if  (cr == row
                    and  cc >= col
                    and  cc <  col + string::length str
                    )
                         draw_cursor txt;
                    fi;
                };
        end;

        #  Draw "str" at "pos" in normal mode 
        #
        fun write_string (txt, pos as CHAR_COORD { 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_COORD { 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_COORD { row, col }, str, highlight)
            =
            {   txt ->   TEXT { size, txt_buf, txt_window, cursor };

                interfere
                    =
                    case cursor

                          { is_on=>TRUE, pos=>CHAR_COORD { 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_COORD { row, col }, count)
            =
            {   txt ->   TEXT { size, txt_buf, txt_window, cursor };

                interfere
                    =
                    case cursor

                         { is_on=>TRUE, pos=>CHAR_COORD { 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_Coord
          | CLEAR_SCR  Char_Coord
          | WRITE_STRING  { pos:  Char_Coord, str:  String }
          | HIGHLIGHT_STRING  { pos:  Char_Coord, str:  String }
          | INSERT_TEXT  { pos:  Char_Coord, str:  String, highlight:  Bool }
          | DELETE_CHARS  { pos:  Char_Coord, count:  Int }
          | MOVE_CURSOR  Char_Coord
          | SET_CURSOR  Bool
          ;

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

        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::open_font (wg::xsession_of root_window) font_name;

                my (char_high, char_wide, _)
                    =
                    font_info font;

                fun realize { kidplug, window, window_size }
                    =
                    {   my  xc::KIDPLUG { from_other', to_mom, ... }
                            =
                            xc::ignore_mouse_and_keyboard  kidplug;

                        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_COORD { row=>0, col=>0 }}
                              };

                        fun imp_loop  text
                            =
                            {
                                fun do_other  envelope
                                    =
                                    case (xc::envelope_contents  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  => (fn plea = {  put_in_mailslot (plea_slot, plea);   take_from_mailslot reply_slot;  }),
                      cmd    => (fn plea =    put_in_mailslot (plea_slot, plea)),

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

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

                                    realize
                                  }
                    };
            };                          # 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
            =
            {   my TEXT_SIZE { rows, cols, ... }
                    =
                    get_info tw;

                { rows, cols };
            };

        fun size_of tw
            =
            {   my TEXT_SIZE { size, ... }
                    =
                    get_info tw;

                size;
            };

        fun pt_to_coord tw point
            =
            {   my  TEXT_SIZE { size, char_high, char_wide, ... }
                    =
                    get_info tw;

                my  xg::POINT { col, row }
                    =
                    xg::point::clip (point, size);

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

        fun coord_to_box  tw  (CHAR_COORD { row, col } )
            =
            {   my TEXT_SIZE { char_wide, char_high, rows, cols, ... }
                    =
                    get_info tw;

                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;

                xg::BOX
                  { 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_Coord, text:  String }
            =
            cmd (HIGHLIGHT_STRING { pos=>at, str=>text } );


        fun insert_ln (TEXT_WIDGET { cmd, ... } ) { lnum, text }
            =
            {   cmd (SCROLL_DOWN { from=>lnum, nlines=>1 } );
                cmd (WRITE_STRING { pos=>CHAR_COORD { 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_Coord, text:  String }
            =
            cmd (INSERT_TEXT { pos=>at, str=>text, highlight=>TRUE } );

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

        fun delete_chars (TEXT_WIDGET { cmd, ... } ) { at:  Char_Coord, 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_COORD { col=>0, row=>0 } ));

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

        fun cursor_pos 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