
#  $Id: editabletextlist.icn,v 1.3 2005-03-25 02:06:41 jeffery Exp $

#
#  A scrollable editable text area.  An {Event} is generated whenever the
#  contents are changed by the user.
#
class EditableTextList : ScrollArea(
   printable,               # The printable characters
   cursor_x,
   cursor_y,
   changed,
   long_line,
   moved,
   startdragx,
   enddragx,
   startdragy,
   enddragy,
   donedrag,
   undolist,
   redolist,
   wordlist,
   noedit,
   modified # not the same as "changed"; external needs-to-be-saved flag
   )

   method select_all()
      startdragx := startdragy := 1
      cursor_y := enddragy := *contents
      cursor_x := enddragx := *actual_line(contents[-1]) + 1
      constrain_line()
      refresh(1)
   end

   method clear_modified()
      modified := &null
   end

 method reset_spell(x,y)
 self.has_focus := 1
 self.cursor_y := y
 self.cursor_x := x

  EraseRectangle(self.cwin, self.tx, self.ty, self.tw, self.th)
  self.constrain_line()
  self.display()

  if text_area_to_high_spell(x,y) = 0 then {
     handle_event(Key_PgDn)
     self.has_focus := &null
  }
  else {
     self.has_focus := &null
     return [self.cursor_x,self.cursor_y]
      }
   return []
 
  end

   #
   method display(buffer_flag)
      EraseRectangle(self.cbwin, self.x, self.y, self.w, self.h)
      DrawSunkenRectangle(self.cbwin, self.x, self.y, self.w, self.h,-2)
      self.text_area_to_buffer()

   #   if *wordlist > 0 then
   #      text_area_to_high()

      (\self.vsb).display(1)
      (\self.hsb).display(1)

      self.do_shading(self.cbwin)
      if /buffer_flag then
         CopyArea(self.cbwin, self.cwin, self.x, self.y, self.w, self.h, self.x, self.y)

      if *wordlist > 0 then
         text_area_to_high()
   end

#________________________________________________________________________
 method text_area_to_high_spell(cx,cy)
   local line, left_pos, nlines, yp, l, sx, ex, blank1, tab1,word,spword,nc,tc
   local r1
   blank1 := ' '
   tab1 := '\t'
   nc := 0
   alnum := &letters ++ &digits
   c2 := &letters++&digits
   c1 := &letters
   sx := 1
   ex := 1
   word := ""
   spword := ""
   r1 := ""

   if *wordlist = 0 then
      return

   spword := wordlist[1]
   line := self.get_line()
   left_pos := self.get_left_pos()

   nlines := (\self.vsb).get_page_size() | *self.contents

   Clip(self.cwin, self.tx, self.ty, self.tw, self.th)
   yp := self.ty + self.line_height / 2
      
   j := cx
 
   every l := self.contents[i := line to self.cursor_y  - 1] do {
       yp +:= self.line_height
   }
   tc := 0
   nc := 0
   l := self.contents[self.cursor_y]

   l := actual_line(l)
 
        
#   every  k := 1 to *r1  do {
#      if any(printable,r1[k]) then 
#          l := l || r1[k]
#   }
  
 
   while  j <  *l do {
      if l[j] == !c1 then { 
           ex := j 
           ex := many(alnum,l,j)
           word := l[j:ex]
           if map(trim(word)) == map(trim(spword)) then {
              self.cursor_x := ex 
              i :=   self.cursor_y 
              EraseRectangle(self.cwin, self.tx, self.ty, self.tw, self.th)
              self.constrain_line()
              self.display()
              left_pos := self.get_left_pos()
              draw23(l, left_pos, yp, i,j,ex,1)
              Clip(self.cwin, self.tx, self.ty, self.tw, self.th)
              return 1
            }  # if map 
            j := ex
      } # if l[j]
      j := j + 1
   }  # while
   

   return 0
   
   end
#____________________________________________________________________
  method text_area_to_high()
   local line, left_pos, nlines, yp, l, sx, ex, blank1, tab1,word,c1,c2,c3
   blank1 := ' '
   tab1 := '\t'
   comma1 := ','
   per1 := '.'
   c2 := &letters++&digits
   c1 := &ascii--blank1--tab1
   c3 := &letters
   sx := 1
   ex := 1
   word := ""

  if *wordlist = 0 then
      return

  word := wordlist[1]

  line := self.get_line()

 # line := actual_line(line)

  left_pos := self.get_left_pos()

   nlines := (\self.vsb).get_page_size() | *self.contents

   Clip(self.cwin, self.tx, self.ty, self.tw, self.th)
   yp := self.ty + self.line_height / 2
   j := 0
     every l := self.contents[i := line to line + nlines - 1] do {
        l := actual_line(l)
        every k := 1 to *wordlist do {
           j := 0
           while  j <  *l do {
             j +:= 1
             if wordlist[k][1] == "'" then {
                if j := find(map(wordlist[k][2:0]),map(l),j) then {
                   ex := j + *(wordlist[k][2:0])
                   draw23(l, left_pos, yp, i,j,ex)
                   j := ex 
                }
            }
            else {
             if member(c2,l[j]) then { 
               ex := many(c2,l,j)
               word := l[j:ex]
               if map(trim(word)) == map(trim(wordlist[k])) then {
                 draw23(l, left_pos, yp, i,j,ex)
                 j := ex
               }
            } # if l[j]
           }
           }  # while
        } # every *wordlist
        yp +:= self.line_height
      }
 
#      Clip(self.cwin, self.tx, self.ty, self.tw, self.th)
       Clip(self.cwin)
     
   return
  
  
   end
#___________________________________________________________________
method draw23(s, left_pos, yp, i,sx,ex,nc)
local s1, s2, newp, fh, asc, desc, yp2 ,s3 
   newp := 0
   s1 := ""
   s2 := ""
   s3 := ""
   fh := WAttrib(self.cwin, "fheight")
   asc := WAttrib(self.cwin, "ascent")
   desc := WAttrib(self.cwin, "descent")

   s :=  actual_line(s)
 
   s1 := s[1:sx]
   s2 := s[sx:ex]
  
   newp := left_pos + TextWidth(self.cwin, s1) 
      
 #  Fg(self.cwin, "very light gray")
   Fg(self.cwin, "light violet")
   yp2 :=  yp + ((WAttrib(self.cwin,"ascent")-
                      WAttrib(self.cwin,"descent"))/2) - asc
   FillRectangle(self.cwin,newp, yp2, TextWidth(self.cwin, s2), fh)
   left_stringr(self.cwin, left_pos, yp, s)
 
   if \nc then
      self.cursor_x :=  *s1 + *s2
  
   end
#_____________________________________________________________

###
   method set_contents(x)
   static printable
   local lst,k,j,s1,new1
   initial { printable := cset(&ascii[33:128]) }

      modified := &null
      lst := []
      reset_drag()
      undolist := []
      redolist := []
      new1 := []

      every j := 1 to *x do {
        s1 := ""
        every k := 1 to *x[j] do {
            if x[j][k] == "\t" then 
                s1 := s1 || x[j][k]
            else if any(printable,x[j][k]) then 
                 s1 := s1 || x[j][k]
        }
       
        put(new1,s1)
      } 

      self.contents := new1
      if *self.contents = 0 then
         #
         # Must have somewhere for the cursor to go.
         #
         self.contents := [""]

      if \ (\self.parent_Dialog).is_open then {
         long_line := &null
         self.cursor_x := self.cursor_y := 1
         compute_and_redisplay()
      }
    #  return x
       return new1
   end

 
   #
   # Move cursor y to line n, and constrain x within range of that line.
   #
   method set_cursor_y(n)
      self.cursor_y := n
      #
      # Constrain x within new line
      #
      self.cursor_x >:= *(actual_line(self.contents[self.cursor_y])) + 1
      return n
   end

   #
   # Move cursor so that it is in the text area, if possible.  May not
   # be possible if cursor at end of line to the left of the text area.
   #
   method constrain_cursor()
   local s, i, j, l
      if self.cursor_y < self.get_line() then
         self.set_cursor_y(self.get_line())
      else if self.cursor_y >= self.get_line() + (\self.vsb).get_page_size() then
         self.set_cursor_y(self.get_line() + (\self.vsb).get_page_size() - 1)

      # use the actual line with the tab offsets to test position
      s := self.contents[self.cursor_y] || " "
      s :=  actual_line(s)

      i := TextWidth(self.cwin, s[1:self.cursor_x])
      j := i + TextWidth(self.cwin, s[self.cursor_x])

      l := self.get_left_pos()
      if self.tx - l > i then {
         while (self.cursor_x < *s) & (TextWidth(self.cwin, s[1:self.cursor_x]) < self.tx - l) do
            self.cursor_x +:= 1
      } else if self.tx - l + self.tw < j then {
         while (self.cursor_x > 1) & TextWidth(self.cwin, s[1:self.cursor_x + 1]) > self.tx - l + self.tw do
            self.cursor_x -:= 1
      }
   end

   #
   # Move the text area displayed so that the cursor is on the screen.
   #
   method constrain_line()
   local s, i, j, l

      if self.cursor_y < self.get_line() then
         self.vsb.set_value(self.cursor_y - 1)
      else if self.cursor_y >= self.get_line() + (\self.vsb).get_page_size() then
         self.vsb.set_value(self.cursor_y - self.vsb.get_page_size())

      s := self.contents[self.cursor_y]

#     use the actual line with the tab offsets to test position swj
      s :=  actual_line(s) || " "

      i := TextWidth(self.cwin, s[1:self.cursor_x])
      j := i + TextWidth(self.cwin, s[self.cursor_x])

      l := self.get_left_pos()
      if self.tx - l > i then
         self.hsb.set_value(i)
      else if self.tx - l + self.tw < j then
         self.hsb.set_value(j - self.tw)
   end

#
#  get_max_width() allows for the actual line with the tabs
#
   method get_max_width()
   local mw, i
      if /long_line then {
         mw := TextWidth(cwin, actual_line(contents[1]))
         long_line := 1
         every i := 2 to *self.contents do
            if mw <:= TextWidth(cwin, actual_line(contents[i])) then
               long_line := i
      } else
         mw := TextWidth(self.cwin, actual_line(contents[long_line]))

      return mw + TextWidth(self.cwin, " ")
   end

   method handle_notify(e)
      if e.get_component() === (\self.vsb | \self.hsb) then {
         self.constrain_cursor()
         self.refresh()
      }
   end

   method handle_event(e)
   local old_contents_size, old_mw
      if E := (\self.vsb).handle_event(e) then {
         #
         # Handled by VSB; amend line number and refresh contents
         #
         self.constrain_cursor()
         self.refresh()
         text_area_to_high()
      } else if E := (\self.hsb).handle_event(e) then {
         #
         # Handled by HSB; amend left offset and refresh
         #
         self.constrain_cursor()
         self.refresh()
         text_area_to_high()
      } else {
        if e === -11 then fail # window closed, nothing to do?
           old_contents_size := *contents
           old_mw := TextWidth(cwin, contents[long_line])
           changed := moved := &null
           if integer(e) = (&lpress | &rpress | &mpress) then
             handle_press(e)
           else if integer(e) = (&ldrag | &rdrag | &mdrag) then
             handle_drag(e)
           else if integer(e) = (&lrelease | &rrelease | &mrelease) then
             handle_release(e)
           else if \self.has_focus then {
             case e of {
               Key_Home : handle_key_home(e)
               Key_End : handle_key_end(e)
               Key_PgUp : handle_key_page_up(e)
               Key_PgDn : handle_key_page_down(e)
               Key_Up : handle_key_up(e)
               Key_Down : handle_key_down(e)
               Key_Left : handle_key_left(e)
               Key_Right : handle_key_right(e)
               "\t" : handle_tab_text(e)
               "\b" : handle_delete_1(e)
               "\r" | "\l": handle_return(e)
               "\^k" : handle_nodefault()
               "\^a" : handle_start_of_line(e)
               "\^e" : handle_end_of_line(e)
               "\d" | "\^d" :  cut_selection(&null,e)
               "\^x" :  handle_nodefault()
               "\^c" :  handle_nodefault()
               "\^v" :  handle_nodefault()
               default : handle_default(e)
             }
           }

         if \moved then {
            #
            # Cursor moved.  Make sure still on screen; update scrollbars,
            # and refresh text.
            #
            self.constrain_line()
            self.refresh(1)
            text_area_to_high()
            return _Event(e, self, 0)
         } else if \changed then {
            if (*contents ~= old_contents_size) | /long_line | (old_mw ~= TextWidth(cwin, contents[long_line])) then {
               #
               # Contents changed.  Re-compute all internal fields, ensure on
               # screen and re-display whole object.
               #
               self.set_internal_fields()
               self.constrain_line()
               self.display()
#               text_area_to_high()
            } else {
               self.constrain_line()
               self.refresh(1)
               text_area_to_high()
            }
            return _Event(e, self, 0)
         }
      }
   end

   method handle_press(e)
   local l, nlines, s, sa
      if ((self.x <= &x < self.x + self.tw + 2 * TX_PADDING) & (self.y  <= &y < self.y + self.th + 2 * TY_PADDING)) then {

         reset_drag()

         #
         # Button down in region - move to cursor position.
         #
         l := (&y - self.ty) / self.line_height
         nlines := self.get_page_size()
         l <:= 0
         l >:= nlines - 1

         self.cursor_y := l + self.get_line()

         #
         # Set cursor_x.
         # Use the actual line with the tab offsets to test position
         #
         s := self.contents[self.cursor_y] || " "
         s :=  actual_line(s)

         # create a mapping for the cursor to skip over the tabs
         sa := map_pos(self.contents[self.cursor_y] || " ")

         self.cursor_x := 1
         l := self.get_left_pos()
         while (self.cursor_x < *s) & (TextWidth(self.cwin, s[1:self.cursor_x + 1]) < &x - l) do
              self.cursor_x := sa[self.cursor_x] + 1
         moved := 1
      }
   end

   method handle_start_of_line(e)
      if self.noedit = 1 then {
         self.has_focus := &null 
         return
         }
      reset_drag()
      cursor_x := 1
      moved := 1
   end

   method handle_end_of_line(e)
   local s
      if self.noedit = 1 then {
         self.has_focus := &null 
         return
         }
      reset_drag()
      s := actual_line(contents[cursor_y])
      cursor_x := *s + 1
      moved := 1
   end

   method handle_key_up(e)
   local sa
      reset_drag()
      if  self.cursor_y - 1 > 0 then
         self.cursor_y := self.cursor_y - 1
      else
         return
      if  self.cursor_x > *(actual_line(self.contents[self.cursor_y])) + 1 then
         self.cursor_x :=  *(actual_line(self.contents[self.cursor_y])) + 1
      else {
         sa := map_pos(self.contents[self.cursor_y] || " ",1)
         self.cursor_x := sa[self.cursor_x]
         }
      moved := 1
   end

   method handle_key_home(e)
      reset_drag()
      cursor_y := cursor_x := 1
      moved := 1
   end

   method handle_key_end(e)
   local s
      reset_drag()
      cursor_y := *contents
      s := actual_line(contents[cursor_y])
      cursor_x := *s + 1
      moved := 1
   end

   method handle_key_down(e)
   local sa
      reset_drag()
      if  self.cursor_y + 1 <= *self.contents then
         self.cursor_y := self.cursor_y + 1
      else
         return
      if  self.cursor_x > *(actual_line(self.contents[self.cursor_y])) + 1 then
         self.cursor_x := *(actual_line(self.contents[self.cursor_y])) + 1
      else {
         sa := map_pos(self.contents[self.cursor_y] || " ",1)
         self.cursor_x := sa[self.cursor_x]
         }
      moved := 1
   end

   method handle_key_left(e)
      local posn, offset, s
      reset_drag()
      if self.cursor_x = 1 then {
         if self.cursor_y > 1 then {
            self.cursor_y -:= 1
            s :=  actual_line(self.contents[self.cursor_y])
            self.cursor_x := *s + 1
            }
         }
      else {
         # self.cursor_x > 1
         offset := offset_pos(self.contents[self.cursor_y],self.cursor_x)
         posn := self.cursor_x  - offset - 1
         if self.contents[self.cursor_y][self.cursor_x  - offset - 1] == "\t" then  {
            offset := offset_pos(self.contents[self.cursor_y],posn,1)
            self.cursor_x :=  posn + offset
         }
         else {
           self.cursor_x -:= 1
         }
      }
      moved := 1
   end

   method handle_key_right(e)
   local s, sa
      reset_drag()
      s := self.contents[self.cursor_y]
      s :=  actual_line(s)

      if self.cursor_x = *s + 1  then {
         if self.cursor_y < *self.contents then {
            self.cursor_x := 1
            self.cursor_y +:= 1
            }
         }
      else {
         sa := map_pos(self.contents[self.cursor_y] || " ")
         self.cursor_x := sa[self.cursor_x] + 1
         }
      moved := 1
   end

   method handle_delete_line(e)
      if self.noedit = 1 then {
         self.has_focus := &null 
         return
         }
      reset_drag()
      if cursor_y < *self.contents then 
         self.contents := self.contents[1:self.cursor_y] ||| self.contents[self.cursor_y + 1 : 0]
      else
         self.contents := self.contents[1:self.cursor_y] ||| [""]

      if long_line = self.cursor_y then
         long_line := &null
      else if long_line > self.cursor_y then
         long_line -:= 1
      changed := 1
      cursor_x := 1
   end

   method handle_delete_1(e, undo)
   local posn, offset, cutlst, undo_del, eolx
      #
      # Delete backspace delete
      #
      if self.noedit = 1 then {
         self.has_focus := &null 
         return
         }
      reset_drag()
      offset := 0
      posn := 0
      cutlst := []

      if self.cursor_x = 1 then {
         if self.cursor_y > 1 then {
            self.cursor_x := *(actual_line(self.contents[self.cursor_y - 1])) + 1

            if /undo then {
               redolist := []
               undo_del := undo_rec("delete_cr",["\r"],self.cursor_x,self.cursor_y-1,0,0,"insert",[],self.cursor_x,self.cursor_y-1,0,0)
               put(undolist,undo_del)
               }
            self.contents[self.cursor_y - 1] ||:= self.contents[self.cursor_y]
            self.contents := self.contents[1:self.cursor_y] ||| self.contents[self.cursor_y + 1 : 0]
            self.cursor_y -:= 1
            changed := 1
            long_line := &null
            }
         }
      else {  # self.cursor_x > 1
         offset := offset_pos(self.contents[self.cursor_y],self.cursor_x)
         posn := self.cursor_x  - offset - 1
         if self.contents[self.cursor_y][self.cursor_x  - offset - 1] == "\t" then  {
            put(cutlst, self.contents[self.cursor_y][self.cursor_x  - offset - 1]) 

            self.contents[self.cursor_y][self.cursor_x  - offset - 1] := ""
            offset := offset_pos(self.contents[self.cursor_y],posn,1)
            self.cursor_x :=  posn + offset

            if /undo then {
               redolist := []
               undo_del := undo_rec("delete_list",cutlst,self.cursor_x,self.cursor_y,0,0,"insert",[],self.cursor_x,self.cursor_y,0,0)
               put(undolist,undo_del)
               }
            } 
         else { # contents ~= "\t"

           if /undo then {
              put(cutlst, self.contents[self.cursor_y][self.cursor_x  - offset - 1]) 
              undo_del := undo_rec("delete_list",cutlst,self.cursor_x-1,self.cursor_y,0,0,"insert",[],self.cursor_x-1,self.cursor_y,0,0)
              put(undolist,undo_del)
              }
           self.contents[self.cursor_y][self.cursor_x  - offset - 1] := ""
           self.cursor_x -:= 1
           }
         changed := 1
         if long_line = self.cursor_y then
           long_line := &null
      }
   end

   method handle_delete_2(e, no_offset)
   local newcursor_x, offset
      #
      # Delete
      #
      if self.noedit = 1 then {
         self.has_focus := &null 
         return
         }
      reset_drag()
      offset := 0

      if /no_offset then 
         offset := offset_pos(self.contents[self.cursor_y],self.cursor_x)

      newcursor_x := self.cursor_x - offset

      if newcursor_x = *contents[cursor_y] + 1 then {
         if self.cursor_y < *contents then {
            self.contents[self.cursor_y] ||:= self.contents[self.cursor_y + 1]
            self.contents := self.contents[1:self.cursor_y + 1] ||| self.contents[self.cursor_y + 2 : 0]
            changed := 1
            long_line := &null
         }
      } else {
         # Cursor not at end of line
         self.contents[self.cursor_y][self.cursor_x  - offset] := ""
         changed := 1
         if long_line = self.cursor_y then
            long_line := &null
      }
   end

   method handle_return(e, undo)
   local offset, s, newcursor_x,j1, undo_info
     if self.noedit = 1 then {
         self.has_focus := &null 
         return
         }
      reset_drag()
      offset := 0
      offset := offset_pos(self.contents[self.cursor_y],self.cursor_x)
      s := self.contents[self.cursor_y]
      newcursor_x := self.cursor_x - offset

#undo 
      if /undo then  {
         redolist := []
         undo_info := undo_rec("insert",[e],self.cursor_x,self.cursor_y,0,0,"delete_cr",[e],self.cursor_x,self.cursor_y,0,0)
            put(undolist,undo_info)
      }
  
      self.contents[self.cursor_y] := s[1:newcursor_x]
      self.contents := self.contents[1:cursor_y + 1] ||| [s[newcursor_x:0]] |||
                         self.contents[cursor_y + 1:0]

      if long_line = self.cursor_y then
         long_line := &null
      else if long_line > self.cursor_y then
         long_line +:= 1

      self.cursor_y +:= 1
      self.cursor_x := 1
      changed := 1
   end

#
# Add tab character at cursor position and tab cursor
#
   method handle_tab_text(e, undo)
   local offset, undo_info
      if self.noedit = 1 then {
         self.has_focus := &null 
         return
         }
      reset_drag()
      offset := 0
      offset := offset_pos(self.contents[self.cursor_y],self.cursor_x)

      if /undo then  {
         redolist := []
         undo_info := undo_rec("insert",[e],self.cursor_x,self.cursor_y,0,0,"delete_list",[e],self.cursor_x,self.cursor_y,0,0)
         put(undolist,undo_info)
         }

      if self.cursor_x = 1 then
         self.contents[self.cursor_y] := e || self.contents[self.cursor_y]
      else
         self.contents[self.cursor_y][self.cursor_x - 1 - offset] ||:= e

      self.cursor_x := self.cursor_x + (8 -  (self.cursor_x-1)%8)
      changed := 1

      if TextWidth(cwin, actual_line(self.contents[self.cursor_y])) >
         TextWidth(cwin, self.contents[long_line]) then
            long_line := self.cursor_y
   end

   method handle_default(e, cntl, undo)
   local offset, undo_info
      #
      # Add any printable character at cursor position
      #
      if self.noedit = 1 then {
         self.has_focus := &null 
         return
         }
      reset_drag()
      offset := 0
      offset := offset_pos(self.contents[self.cursor_y],self.cursor_x)

      if /cntl &  type(e) == "string" & &control then
         return 
        
   #  if type(e) == "string" & not(&control | &meta) & any(printable, e) then {
      if type(e) == "string" & not(&meta) & any(printable, e) then {

         if /undo then  {
            redolist := []
            undo_info := undo_rec("insert",[e],self.cursor_x,self.cursor_y,0,0,"delete_list",[e],self.cursor_x,self.cursor_y,0,0)
            put(undolist,undo_info)
            }
   
         if self.cursor_x = 1 then
            self.contents[self.cursor_y] := e || self.contents[self.cursor_y]
         else
            self.contents[self.cursor_y][self.cursor_x - 1 - offset] ||:= e

         self.cursor_x +:= 1
         changed := 1
         if TextWidth(cwin, self.contents[self.cursor_y]) > TextWidth(cwin, self.contents[long_line]) then
            long_line := self.cursor_y
      }
end

#
# The undo list is built for each event. This method gets the last
# item off the list and undos it using the same routines. A non-null
# parameter is passed to cut_selection, set_selection, and handle_return 
# so that these undo actions are not put back on the undo list. 
#
   method handle_undo(e, no_redo)
      redolist := []
      if *undolist = 0 then
         return
    
      if self.noedit = 1 then {
         self.has_focus := &null 
         return
      }
 
      self.has_focus := 1
      undo_info  := pull(undolist)
      case undo_info.undo_type of  {
      "insert" : { 
         reset_drag()
         self.cursor_x :=  undo_info.undo_x
         self.cursor_y :=  undo_info.undo_y
         if /no_redo then
            put(redolist,undo_info)
         cut_selection(1) # delete don't build undo
         }
      "delete_list" : {
         reset_drag() #
         self.cursor_x := undo_info.undo_x 
         self.cursor_y := undo_info.undo_y 
         if /no_redo then
            put(redolist,undo_info)
         set_selection(undo_info.undo_list,1)
         self.has_focus := &null
         return
         }
      "insert_list" : {
         reset_drag()
         startdragx := undo_info.undo_x
         startdragy :=  undo_info.undo_y
         enddragx :=  undo_info.undoend_x
         enddragy := undo_info.undoend_y
         if /no_redo then
            put(redolist,undo_info)
         cut_selection(1) 
         self.has_focus := &null
         return
         }
      "delete_cr" : {
         reset_drag()
         self.cursor_x := undo_info.undo_x 
         self.cursor_y := undo_info.undo_y 
         if /no_redo then
            put(redolist,undo_info)
         handle_return("\r",1)
         self.set_internal_fields()
         self.constrain_line()
         self.display()
#         text_area_to_high()
         self.has_focus := &null
         return
         }      
      }

   self.has_focus := &null
   end

#
# Redo the last undo. Only one redo allowed.
# Undo puts the last undo on the redolist so redo pulls
# it off and calls undo with a parameter no_redo so the
# redo does not get put on the redolist again.
#
   method handle_redo(e)
   local rec1,redo

      if *redolist = 0 then
         return

      if self.noedit = 1 then {
        self.has_focus := &null 
        return
      }
      rec1 :=  pull(redolist)
      redo := undo_rec("",[],0,0,0,0,"",[],0,0,0,0)

      redo.undo_type :=  rec1.redo_type
      redo.undo_list := rec1.redo_list
      redo.undo_x := rec1.redo_x
      redo.undo_y := rec1.redo_y
      redo.undoend_x := rec1.redoend_x
      redo.undoend_y := rec1.redoend_y

      redo.redo_type :=  ""
      redo.redo_list := []
      redo.redo_x := 0
      redo.redo_y := 0
      redo.redoend_x := 0
      redo.redoend_y := 0

      put(undolist,rec1)
      put(undolist,redo)
      handle_undo(e,1)
   end

   method handle_key_page_up(e)
   local i
      reset_drag()
      if i := (\self.vsb).get_value() then {
         self.vsb.set_value(i - self.vsb.page_size)
         self.constrain_cursor()
         self.refresh()
         text_area_to_high()
         }
   end

   method handle_key_page_down(e)
   local i
      reset_drag()
      if i := (\self.vsb).get_value() then {
         self.vsb.set_value(i + self.vsb.page_size)
         self.constrain_cursor()
         self.refresh()
         text_area_to_high()
         }
   end

   method resize()
      self$ScrollArea.resize()
      self.constrain_line()
   end

   method handle_release(e)
      if ((self.x <= &x < self.x + self.tw + 2 * TX_PADDING) &
            (self.y  <= &y < self.y + self.th + 2 * TY_PADDING)) then {
         donedrag := 0
         moved := 1
         }
   end

   method reset_drag()
      enddragx := startdragx
      enddragy := startdragy
   end

   method handle_drag(e)
   local l, nlines, s, sa, s1

      if ((self.x <= &x < self.x + self.tw + 2 * TX_PADDING) &
            (self.y  <= &y < self.y + self.th + 2 * TY_PADDING)) then {

         #
         # Button down in region - move to cursor position.
         #
         l := (&y - self.ty) / self.line_height
         nlines := self.get_page_size()

         l <:= 0
         l >:= nlines - 1

         self.cursor_y := l + self.get_line()

         if (self.cursor_y > 1) & (self.cursor_y = self.get_line()) then {
            self.vsb.set_value(self.cursor_y - 2)
            }

         if self.cursor_y + 1  >= self.get_line() + (\self.vsb).get_page_size()
           then {
            self.vsb.set_value((self.cursor_y + 1) - self.vsb.get_page_size())
            }

        #
        # Set cursor_x
        #
#     use the actual line with the tab offsets to test position swj

        s := self.contents[self.cursor_y] || " "
        s :=  actual_line(s)

#     create a mapping for the cursor to skip over the tabs
        sa := map_pos(self.contents[self.cursor_y] || " ")
    
        self.cursor_x := 1
        l := self.get_left_pos()
        while (self.cursor_x < *s) & (TextWidth(self.cwin, s[1:self.cursor_x + 1]) < &x - l)
        do {
           self.cursor_x := sa[self.cursor_x] + 1
           if self.cursor_x = *s + 1  then {
             if self.cursor_y < *self.contents then {
                 self.cursor_y +:= 1
                 s1 := actual_line(self.contents[self.cursor_y]) || " "
                 self.cursor_x := *s1
              }
            }
          }  # while

  
      if donedrag = 0 then {
         startdragx := self.cursor_x
         startdragy := self.cursor_y
         donedrag := 1
         }

       enddragx := self.cursor_x
       enddragy := self.cursor_y
       moved := 1
       } # if in region
   end

#
# added to prevent reset of drag positions when ^x,^c,^v 
#
method handle_nodefault()
   if self.noedit = 1 then {
      self.has_focus := &null 
      return
      }
   return
end

#
#  method added to support paste
#  parameter s is a list of strings
#
method set_selection(slst, undo)
local stng,j,k,endx,endy,endpos,offset,slast,newcursor_x,s,s2,oldcursor_y,oldcursor_x,undo_info,s3 
   self.has_focus := 1
   stng := ""
   slast := ""
   s2 := ""
   s := ""
   s3 := ""

   if  /slst  | *slst < 0  then
      return

   offset := 0
   offset := offset_pos(self.contents[self.cursor_y],self.cursor_x)
   
   if /undo then {
      redolist := []
      undo := 1 # do not build list in handle_default because built here
      if *slst > 1 then {
         endy := self.cursor_y  + *slst - 1 
         slast :=  actual_line(slst[*slst])  # size of last string in list
         endx := *slast + 1 # one more for drag
         }
      else {  # one line need to get the position in whole line for the tabs
         endy := self.cursor_y
         s := self.contents[self.cursor_y]
         newcursor_x := self.cursor_x - offset
         s3 := s[1:newcursor_x] || slst[1]
         slast :=  actual_line(s3)  # size of last string in list
         endx :=  *slast + 1
         }
      undo_info := undo_rec("insert_list",[],self.cursor_x,self.cursor_y,endx,endy,"delete_list",slst,self.cursor_x,self.cursor_y,0,0)
      put(undolist,undo_info)
   }

   stng := slst[1]
   if *slst = 1 & *stng = 1 then {
      if stng == "\t" then 
         handle_tab_text(stng,undo)
       else
          handle_default(stng,1,undo)
       self.set_internal_fields()
       self.constrain_line()
       self.display()
#       text_area_to_high()
       self.has_focus := &null
       return _Event(e, self, 0)
     #  return
   }

   s := self.contents[self.cursor_y]
   oldcursor_x := self.cursor_x
   oldcursor_y := self.cursor_y
   newcursor_x := self.cursor_x - offset
     
   self.contents[self.cursor_y] := s[1:newcursor_x] || stng 
   s2 :=  s[newcursor_x:0]

   self.contents := self.contents[1:cursor_y + 1] ||| self.contents[cursor_y + 1:0]

   self.cursor_y +:= 1
   self.cursor_x := 1
      
   every j := 2 to *slst  do {
      stng := slst[j]
      s :=  self.contents[self.cursor_y]
      if self.cursor_y > *self.contents then
         put(self.contents,stng)
      else {
         self.contents[self.cursor_y] :=  stng
         self.contents := self.contents[1:cursor_y + 1]|||[s]|||self.contents[cursor_y + 1:0]
      }
      self.cursor_y +:= 1
      self.cursor_x := 1
   }
  
   self.cursor_y  := self.cursor_y -  1
   self.contents[self.cursor_y] :=  self.contents[self.cursor_y] || s2

   long_line := &null

   reset_drag()

   self.cursor_y :=  oldcursor_y 
   self.cursor_x :=  oldcursor_x
   self.set_internal_fields()
   self.constrain_line()
   self.display()
#   text_area_to_high()

   self.has_focus := &null
   return _Event(e, self, 0)
  end

#  method added to retieve a highlighted section swj 
#  returns a list of strings for Copy
##
method get_selection()
local st, lst, l, i, sx
   st := ""
   lst := []
   l := ""
    #  self.has_focus := &null
   #  return just one character if nothing is highlighted  
   if (enddragx = startdragx ) & (enddragy = startdragy) then {
      offset := offset_pos(self.contents[self.cursor_y],self.cursor_x)
      sx := self.cursor_x - offset
      l := self.contents[self.cursor_y]
      st := l[sx:sx + 1]
      put(lst,st)
      return lst
      }
   every l := self.contents[i := 1 to 1 + *self.contents - 1] do {
      st := get_region(i)
      if \st then
         put(lst,st)
      } 
      return lst
   end

#
#  returns the string that is highlighted for each line of self.contents
#  s is the line of self.contents and i is line number or y position
# 
method get_region(i)
local ex, sx, offset, s2
   s2 := ""
   offset := offset_pos(self.contents[i],startdragx)
   sx := startdragx - offset
   offset := offset_pos(self.contents[i],enddragx)
   ex := enddragx - offset

   #  if no vertical drag just current line
   if (i = startdragy) & (startdragy = enddragy) then {
      if startdragx < enddragx then    # forward drag
         return self.contents[i][sx:ex] 
      if startdragx > enddragx then    # backward drag
         return self.contents[i][ex:sx]
      }

   #  if drag on current line and a vertical drag
   #  for forward drag need to get whole line for backward
   #  need to get line up to start of drag

   if i = startdragy then {
      if i < enddragy then     # for forward drag get the whole line
         return self.contents[i][sx:*(self.contents[i])+1]
      if  i > enddragy  then  # reverse drag get the 1st part to start x
         return self.contents[i][1:sx]
      }

   # for the last endragy line  on vertical drag

   if i = enddragy then {
      if i > startdragy then  # for forward drag
         return self.contents[i][1:ex]
      if i < startdragy then  # for reverse drag
         if ex <=  *(self.contents[i]) then 
            return self.contents[i][ex:0]
      }

   #  fill in all the lines in between
   #  for forward drag         
   if i < enddragy & i > startdragy then  #  for forward drag     
      return self.contents[i]
   if i > enddragy & i < startdragy then  # reverse
      return self.contents[i]
  
   return
   end

#
#  This method added to support Cut
#  It removes the highlighted region from self.contents
#  This cut was modeled after Emacs editor and therefore removes
#  the carriage return at the end of lines so they are merged. 
#
method cut_selection(undo,ev)
local l, lst, ex, ey, sx, sy, i, flag1,flag2, undo_info, offset, newcursor_x 
   l := ""
   lst := []
   flag1 := ["n"] # flags to indicate if A all or P partial cut of line
   flag2 := ["n"] # If both the first and last line are A then we insert a cr
   cutlst := []   # If both the first and last line are P then we delete a cr to merge
   offset := 0
   if self.noedit = 1 then {
      self.has_focus := &null
      return
      }
  
   #
   #  keep starting and ending drag positions because they will be reset
   #  when handle_delete_2 and handle_delete_line etc are called.
   #
   ex := enddragx 
   ey := enddragy
   sx := startdragx
   sy := startdragy
   
   # No drag just delete character under the cursor
   if (enddragx = startdragx ) & (enddragy = startdragy) then {
      if /undo then {
         redolist := []
         offset := offset_pos(self.contents[self.cursor_y],self.cursor_x)
         newcursor_x := self.cursor_x-offset
         put(cutlst,self.contents[self.cursor_y][newcursor_x]) 
         undo_info := undo_rec("delete_list",cutlst,self.cursor_x,self.cursor_y,0,0,"insert",[],newcursor_x,self.cursor_y,0,0)
         put(undolist,undo_info)
         }
      handle_delete_2("\d")
      self.max_width := get_max_width()
      self.set_internal_fields()
      self.display()
#      text_area_to_high()
      if /ev then 
        self.has_focus := &null
      return 
      }
 
   if /undo then {
      redolist := []
      cutlst :=  get_selection()
      if sy = ey then 
         if ex > sx then {      # forward
            undo_info := undo_rec("delete_list",cutlst,sx,sy,0,0,"insert_list",[],sx,sy,ex,ey)
            put(undolist,undo_info)
            }
         else {               # bottom to top drag
           undo_info := undo_rec("delete_list",cutlst,ex,ey,0,0,"insert_list",[],ex,ey,sx,sy)
           put(undolist,undo_info)
           }
      else if ey > sy then {     # top to bottom drag
         undo_info := undo_rec("delete_list",cutlst,sx,sy,0,0,"insert_list",[],sx,sy,ex,ey)
         put(undolist,undo_info)
         }
      else if ey < sy then {       # bottom to top drag
         undo_info := undo_rec("delete_list",cutlst,ex,ey,0,0,"insert_list",[],ex,ey,sx,sy)
         put(undolist,undo_info)
         }
      }

   undo := 1
   if sy = ey then {         # only horizontal drag
      l := self.contents[sy] 
      i := sy
      get_cutregion_1_line(i, sx, ex,undo,flag1)
      }
   else {
      if ey > sy then {       # top to bottom drag
         i := ey
         get_cutregion_last(i, ex, ey, sx, sy, flag2)
         # go through next loop bottom up because lines are deleted
         i := ey - 1 
         while i > sy do {
            l := self.contents[i] 
            get_cutregion_mid(l, i, ex, ey, sx, sy)
            i := i - 1
            }
         i := sy
         get_cutregion_first(i, ex, ey, sx, sy, flag1)
         if flag1[1] == "P" &  flag2[1] == "P"  then # P (Partial) lines cut merge
            get_cutregion_cr(i, ex, ey, sx, sy)
         if flag1[1] == "A" &  flag2[1] == "A"  then {# A All of lines cut add cr
            self.cursor_y := sy
            self.cursor_x := 1 
            handle_return("\r",undo)
            self.cursor_y := sy
            self.cursor_x := 1 
            } 
         }
      else { # bottom to top drag
         i := sy  # cut line from start of drag
         get_cutregion_last(i, ex, ey, sx, sy, flag2)
         i := sy - 1
         while i > ey do {
            l := self.contents[i] 
            get_cutregion_mid(l, i, ex, ey, sx, sy)
            self.max_width := get_max_width()
            i := i - 1
            }
         i := ey   # cut line from end of drag
         get_cutregion_first(i, ex, ey, sx, sy, flag1)
         if flag1[1] == "P" &  flag2[1] == "P"  then # P (Partial) lines cut merge
            get_cutregion_cr(i, ex, ey, sx, sy)
         if flag1[1] == "A" &  flag2[1] == "A"  then {# A All of lines cut add cr
            self.cursor_y := ey
            self.cursor_x := 1 
            handle_return("\r",undo)
            self.cursor_y := ey
            self.cursor_x := 1 
            } 
         } # bottom to top drag
      }# end else ey > sy

   if ey = sy then {
      self.cursor_y := sy 
      if sx > ex then         # reverse drag
         self.cursor_x := ex
      else 
         self.cursor_x := sx
      }
   if ey > sy then {       # top to bottom drag
      self.cursor_y := sy 
      self.cursor_x := sx
      }
   if ey < sy then {        # bottom to top drag
      self.cursor_y := ey 
      self.cursor_x := ex
      }

   reset_drag()
   self.set_internal_fields()
   self.constrain_line()
   self.display()
#   text_area_to_high()
   if /ev then 
     self.has_focus := &null

end

#
#  method to cut the lines in the middle of the highlighted region
#
method get_cutregion_mid(s, i, ex, ey, sx, sy)
   s :=  actual_line(s)
   self.cursor_y := i
   self.cursor_x := ex 
     
   if i < ey & i > sy then { # for forward drag     
      handle_delete_line("\^k")
      self.max_width := get_max_width()
      return 
      }
   if i > ey & i < sy then {  # reverse drag
      handle_delete_line("\^k")
      self.max_width := get_max_width()
      return 
      }
   end

#
#  method to cut on just one line no vertical drag
#
method get_cutregion_1_line(i, sx, ex,undo,flag1)
local j, offset

   j := 0
   self.cursor_y := i
   offset := offset_pos(self.contents[self.cursor_y],sx)
   sx := sx - offset
   offset := offset_pos(self.contents[self.cursor_y],ex)
   ex := ex - offset

   #  if no vertical drag just current line
   if sx < ex then {   # forward drag
      every j := sx to ex - 1 do {
         self.cursor_x := sx
         handle_delete_2("\d",1)
         self.max_width := get_max_width()
         }
      return
      }
   if sx > ex then {    # backward drag
      every j := ex to sx - 1 do {
          self.cursor_x := ex 
          handle_delete_2("\d",1)
          self.max_width := get_max_width()
          }
      }
   end

#  method to cut the highlighted regions of the first and last lines
#  when more than one line is highlighted 
#  set flag1 if drag on last line does not include the entire line.
#  This will indicate that the carriage return on the previous line
#  must be deleted so the lines will be merged and will behave like
#  the Emacs Editor.
   method get_cutregion_first(i, ex, ey, sx, sy, flag2)
   local j
   j := 0
   
   self.cursor_y := i
   s  := self.contents[self.cursor_y] 
  
#  if drag on current line and a vertical drag
#  for forward drag need to get whole line for backward
#  need to get line up to start of drag

   if sy < ey then {  # for forward drag get the whole line
      offset := offset_pos(self.contents[self.cursor_y],sx)
      sx := sx - offset
      if sx = 1 then {
         handle_delete_line("\^k")
         self.max_width := get_max_width()
         flag2[1] := "A"  # All of line cut
         return 
         }
      every j := sx to *s do {
         self.cursor_x := sx
         handle_delete_2("\d",1)
         self.max_width := get_max_width()
         }
      flag2[1] := "P"     # part of line cut
      return 
      }
   if sy > ey then { # reverse drag get the 1st part to start x
      offset := offset_pos(self.contents[self.cursor_y],ex)
      ex := ex - offset
      if ex = 1 then {
         handle_delete_line("\^k")
         self.max_width := get_max_width()
         flag2[1] := "A"  # all of line cut
         return 
         }
      every j := ex to *s do {
         self.cursor_x := ex 
         handle_delete_2("\d",1)
         self.max_width := get_max_width()
         }
      flag2[1] := "P"   # part of line cut
      return 
      }
   end

method get_cutregion_last(i,ex, ey, sx, sy, flag1)
local j,offset
   j := 0
   offset := 0
 
   self.cursor_y := i
   s  := self.contents[self.cursor_y]
 
   if ey > sy then { # for forward drag
      offset := offset_pos(self.contents[self.cursor_y],ex)
      ex := ex - offset
      if ex >= *s then {
         handle_delete_line("\^k")
         self.max_width := get_max_width()
         flag1[1] := "A"
         return 
         }
      every j := 1 to ex - 1 do {
         self.cursor_x := 1
         handle_delete_2("\d",1)
         self.max_width := get_max_width()
         flag1[1] := "P"
         }
      return
      }

   if ey < sy then { # for reverse drag
      offset := offset_pos(self.contents[self.cursor_y],sx)
      sx := sx - offset
      if sx >= *s then {
         handle_delete_line("\^k")
         self.max_width := get_max_width()
         flag1[1] := "A"
         return 
         }
      every j := 1 to sx - 1 do {
         self.cursor_x := 1
         handle_delete_2("\d",1)
         self.max_width := get_max_width()
         }
      flag1[1] := "P"
      }
   return 
end

#
#  method to remove carriage return on the line before(forward drag)
#  or after(backward drag). This will merge the two lines. 
#
method get_cutregion_cr(i, ex, ey, sx, sy)
local s

   self.cursor_y := i
   s  := self.contents[self.cursor_y]  
   s :=  actual_line(s)

   if *s = 0 then
      return
  
# if drag on current line and a vertical drag
# for forward drag need to get whole line for backward
# need to get line up to start of drag

   if i = sy then {
      if i < ey then {  # for forward drag get the whole line
         self.cursor_x := *s + 1
         handle_delete_2("\d")
         self.max_width := get_max_width()
         return 
         }
      }

   if i = ey then {
      if i < sy then { # for reverse drag
         self.cursor_x := *s + 1 
         handle_delete_2("\d")
         self.max_width := get_max_width()
         }
      return 
      }
   end

#
#  Added Editable Textlist version of draw
#
method draw(s, left_pos, yp, i)
local s1, s2, newp, fh, asc, desc, yp2  
   newp := 0
   s1 := ""
   s2 := ""
   fh := WAttrib(self.cbwin, "fheight")
   asc := WAttrib(self.cbwin, "ascent")
   desc := WAttrib(self.cbwin, "descent")

   s :=  actual_line(s)

#  if no vertical drag just current line
   if (i = startdragy) & (startdragy = enddragy)  then {
      if startdragx < enddragx then {   # forward drag
         s1 := s[1:startdragx]
         s2 := s[startdragx:enddragx]
         newp := left_pos + TextWidth(self.cbwin, s1)
         }

      if startdragx > enddragx then {    # backward drag
         s1 := s[1:enddragx+1]
         s2 := s[enddragx+1:startdragx]
         newp := left_pos + TextWidth(self.cbwin, s1)
         }

      Fg(self.cbwin, "light gray")
      yp2 :=  yp + ((WAttrib(self.cbwin,"ascent")-
                      WAttrib(self.cbwin,"descent"))/2) - asc
      FillRectangle(self.cbwin,newp, yp2, TextWidth(self.cwin, s2), fh)
      }

#  if drag on current line and a vertical drag
#  for forward drag need to get whole line for backward
#  need to get line up to start of drag

   if i = startdragy then {
      if i <  enddragy then {  # for forward drag get the whole line
         s1 := s[1:startdragx]
         s2 :=  s[startdragx:0]
         newp := left_pos + TextWidth(self.cbwin, s1)
         }
   if i > enddragy then { # reverse drag get the 1st part to start x
      if startdragx  >= *s then
         s2 := s 
      else
         s2 := s[1:startdragx]
      newp := left_pos
      }
   Fg(self.cbwin, "light gray")
   yp2 :=  yp + ((WAttrib(self.cbwin,"ascent")- WAttrib(self.cbwin,"descent"))/2) - asc
   FillRectangle(self.cbwin,newp, yp2, TextWidth(self.cwin, s2), fh)
   }

   #  for the last endragy line  on vertical drag
   if i = enddragy then {
     if i > startdragy then { # for forward drag
        if enddragx > *s then
           s2 := s[1:*s+1]
        else
           s2 := s[1:enddragx]
        newp := left_pos
     }
     if i < startdragy then { # for reverse drag
        if enddragx <= *s then {
           s1 := s[1:enddragx+1]
           s2 := s[enddragx+1:0]
           newp := left_pos + TextWidth(self.cbwin, s1)
           }
        }
     Fg(self.cbwin, "light gray")
     yp2 :=  yp + ((WAttrib(self.cbwin,"ascent")-
                    WAttrib(self.cbwin,"descent"))/2) - asc
     FillRectangle(self.cbwin,newp, yp2, TextWidth(self.cwin, s2), fh)
     }

   #  fill in all the lines in between
   #  for forward drag         OR       for reverse drag
   if (i < enddragy & i > startdragy) | (i > enddragy & i < startdragy)  then {
      Fg(self.cbwin, "light gray")
      yp2 :=  yp + ((WAttrib(self.cbwin,"ascent")-
                    WAttrib(self.cbwin,"descent"))/2) - asc
      FillRectangle(self.cbwin,left_pos, yp2, TextWidth(self.cwin, s), fh)
      }
   left_stringr(self.cbwin, left_pos, yp, s)

   #  cursor is a BLOCK OR a rectangle
   if i = \self.cursor_y then {
      s ||:= " "
      if \self.has_focus then {
         FillRectangle(rev, left_pos + TextWidth(self.cbwin, s[1:self.cursor_x]), 1 + yp - self.line_height / 2, TextWidth(self.cbwin, s[self.cursor_x]), self.line_height)

         }
      else {
         Rectangle(self.cbwin, left_pos + TextWidth(self.cbwin, s[1:self.cursor_x]), 1 + yp - self.line_height / 2, TextWidth(self.cbwin, s[self.cursor_x]), self.line_height)
         }
      }
end

# 
#
#  This method returns the offset(or number of added blanks because
#  of the tabs) up to the end position. If the use_selfcontents parameter is
#  null then the number of blanks up to the position (endpos) in the
#  actual line (the line with the blanks that is displayed) is returned.
#  If  use_selfcontents is not null, then the number of blanks that would be  
#  inserted up to the position (endpos) in self.contents is returned. 
#  
   method offset_pos(tab_line,endpos,use_selfcontents)  
   local posx, sblnks, linepos, numtabs, x12
   posx := 0
   sblnks := 0
   linepos := 0
   numtabs := 0
 
   tab_line ? {
      while x12 := move(1) do {
         linepos := linepos + 1
         if /use_selfcontents then {
            if posx == endpos - 1  then # stop at the position in the line displayed
               return sblnks - numtabs
            } 
         else {
            if linepos == endpos  then  # stop where at endpos in self.contents 
               return  sblnks - numtabs
             }
         if x12 == "\t" then {
            numtabs +:= 1
            posx +:= 1
            sblnks +:= 8 - (posx-1)%8
            posx +:= (8 -  (posx-1)%8) - 1
            }
         else
            posx +:= 1
         } # while
      }

      #  subtract numtabs the positions "\t" since we already counted them
      return sblnks - numtabs
   end

#  Create a list mapping the cursor postion t
#  Create a list mapping the cursor postion to the correct position
#  This allows the cursor to skip over the tab positions and jump
#  to the next nontab position. If vertical_key is null, then it
#  is a horizantal jump and needs to move an extra position. If the
#  vertical_key is not null, then it is a vertical jump and it does
#  not need to move forward an extra position just straight up or down.
   
method map_pos(pos_line, vertical_key) 
local posx, sblnks, pos_line2, x12, tabpos,printable
   printable := cset(&ascii[33:128])
   posx := 0
   sblnks := 0
   pos_line2 := list(*pos_line,1)

   pos_line ? {
      while x12 := move(1) do {
     
         if x12 == "\t" then {
            posx +:= 1

         if /vertical_key then
            tabpos := posx + 8 -  (posx-1)%8 - 1
         else
            tabpos := posx + 8 -  (posx-1)%8
         pos_line2 :=  handle_map_pos(posx,pos_line2,tabpos)
         posx +:= (8 -  (posx-1)%8) - 1
         }
       else {
        # if any(printable,x12) then { 
            posx +:= 1
            pos_line2[posx] := posx
        # }
         }
      }
      
   }

   return pos_line2
   end

#
# helper method for map_pos
method handle_map_pos(posx, pos_line2, tabpos)
local tablen, seg1, seg2, limit, j, i

   tablen := []
   seg1 := []
   seg2 := []

   limit := 8 - (posx-1)%8
   j := 0
   while j < limit do {
      put(tablen,tabpos)
      j +:= 1
      }

   seg1 := pos_line2[1:posx]

   every i := posx + 1 to *pos_line2 do
     put(seg2,pos_line2[i])

   pos_line2 := seg1 ||| tablen ||| seg2
   return pos_line2
   end

   initially
      self$ScrollArea.initially()
      printable := cset(&ascii[33:128])
      self.cursor_x := self.cursor_y := 1
      self.keeps_tabs := 1
      accepts_tab_focus_flag := &null
      startdragx := 1
      enddragx := 1
      startdragy := 1
      enddragy := 1
      donedrag := 0
      undolist := []
      redolist := []
      wordlist := []
      noedit := 0
end
