Source file editabletextlist.icn |
# $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
This page produced by UniDoc on 2021/04/15 @ 23:59:43.