Source file scrollarea.icn |
# $Id: scrollarea.icn,v 1.1 2003-05-31 06:09:03 jeffery Exp $
$define TX_PADDING (DEFAULT_TEXT_X_SURROUND + 3)
$define TY_PADDING (DEFAULT_TEXT_Y_SURROUND + 3)
##
# This class acts as a superclass for any object which wishes
# to display several items within a scrollable area.
#
class ScrollArea : Component(
contents,
rev, # Cloned reverse field window
max_width,
tx,
ty,
tw,
th,
hsb,
vsb,
last_refresh_x,
last_refresh_y,
line_height,
checked,
select_one,
select_many,
which_down,
going_up,
prev_down,
is_held,
tab_line
)
##
# Set the data to be displayed.
# @param x The list of data.
#
method set_contents(x)
self.contents := x
#
# Expand/contract list if necessary
#
while *self.checked < *x do put(self.checked)
while *self.checked > *x do pull(self.checked)
compute_and_redisplay()
return x
end
##
# Set the checked (highlighted) lines.
# @param l A list. Each element in the list corresponds to an element in
# @ the data. If the element is not {&null}, the line is checked.
#
method set_checked(l)
return self.checked := l
end
##
# Get the checked lines.
# @return A list corresponding to the data. If an element is not {&null}, then
# @ the corresponding line is checked.
#
method get_checked()
return self.checked
end
##
# Clear the checked selections.
#
method clear_selections()
self.checked := list(*contents)
redisplay()
return
end
##
# Configure the object so that only one line may be highlighted
#
method set_select_one()
self.select_one := 1
end
##
# Configure the object so that several lines may be highlighted
#
method set_select_many()
self.select_many := 1
end
##
# Return item currently under the clicked cursor
# @return The item number
#
method get_which_down()
return \self.which_down
end
##
# Return object currently under the clicked cursor
# @return The object
#
method object_get_which_down()
return self.contents[\self.which_down]
end
##
# Return the item previously under the clicked cursor
# @return The item number
#
method get_prev_down()
return \self.prev_down
end
##
# Return object currently under the clicked cursor
# @return The object
#
method object_get_prev_down()
return self.contents[\self.prev_down]
end
##
# Return a list of items checked (highlighted)
# @return A list of items currently checked
#
method get_selections()
local r, i
r := []
every i := 1 to *self.checked do
if \self.checked[i] then
put(r, i)
return r
end
##
# Return a list of objects checked (highlighted)
# @return A list of objects currently checked
#
method object_get_selections()
local r, i
r := []
every i := 1 to *self.checked do
if \self.checked[i] then
put(r, self.contents[i])
return r
end
##
# Set the current selections to the list l, which is a list of
# item numbers.
# @param l The list of item numbers.
#
method set_selections(l)
self.checked := list(*self.contents)
every self.checked[!l] := 1
redisplay()
end
##
# Set the current selections to the list l, which is a list of objects
# @param l The list of objects.
#
method object_set_selections(l)
self.checked := []
every e := !self.contents do
put(self.checked, if e === !l then 1 else &null)
redisplay()
end
##
# Return the contents of the {ScrollArea}
#
method get_contents()
return self.contents
end
method finally()
self$Component.finally()
(\self.vsb)$finally()
(\self.hsb)$finally()
self.vsb := self.hsb := &null
end
method handle_key_page_up(e)
local i
if i := (\self.vsb)$get_value() then {
self.vsb$set_value(i - self.vsb.page_size)
self$refresh()
}
end
method handle_key_page_down(e)
local i
if i := (\self.vsb)$get_value() then {
self.vsb$set_value(i + self.vsb.page_size)
self$refresh()
}
end
method handle_key_up(e)
local i
if i := (\self.vsb)$get_value() then {
self.vsb$set_value(i - 1)
self$refresh()
}
end
method handle_key_down(e)
local i
if i := (\self.vsb)$get_value() then {
self.vsb$set_value(i + 1)
self$refresh()
}
end
method handle_key_left(e)
local i
if i := (\self.hsb)$get_value() then {
self.hsb$set_value(i - self.hsb.increment_size)
self$refresh()
}
end
method handle_key_right(e)
local i
if i := (\self.hsb)$get_value() then {
self.hsb$set_value(i + self.hsb.increment_size)
self$refresh()
}
end
method handle_key_home(e)
if (\self.vsb)$set_value(0) then
self$refresh()
end
method handle_key_end(e)
if (\self.vsb)$set_value(*self.contents - 1) then
self$refresh()
end
method handle_notify(e)
if e$get_component() === (\self.vsb | \self.hsb) then
self$refresh()
end
method handle_press(e)
local l
if \ (self.select_one | self.select_many) & (self.tx <= &x < self.tx + self.tw) & (self.ty <= &y < self.ty + self.th) then {
#
# Compute line clicked
#
l := (&y - self.ty) / self.line_height + self$get_line()
if self.which_down := (self$get_last_line() >= l) then {
if \self.select_many & (&shift | &control) then {
#
# Click with shift/ctrl - the state will end here.
#
if &control then
self.checked[self.which_down] := if /self.checked[self.which_down] then 1 else &null
else {
#
# &shift
#
if \self.prev_down then {
if self.prev_down > self.which_down then
every self.checked[self.which_down to self.prev_down] := 1
else
every self.checked[self.prev_down to self.which_down] := 1
} else
self.checked[self.which_down] := 1
}
self.prev_down := self.which_down
self.which_down := &null
self$refresh(1)
return _Event(e, self, 1)
} else {
#
# Normal click down
#
self.is_held := 1
self.checked := list(*self.contents)
self$refresh(1)
return _Event(e, self, 0)
}
}
}
end
method end_state()
if \self.is_held then {
self.is_held := &null
stop_ticker()
#
# Clear flag, refresh, return event
#
self.checked := list(*self.contents)
self.checked[self.which_down] := 1
self.which_down := &null
self.prev_down := self.which_down
self$refresh(1)
}
end
method handle_drag(e)
local old_down, l
if \self.is_held then {
#
# Mouse drag - save present marked line
#
old_down := self.which_down
#
# Test for &null which down just to satisfy the drop down list
# classes which set is_held manually. In normal use, which_down
# is never &null.
#
if &y < self.ty then {
self.going_up := 1
if \self.which_down & /self.ticker_rate then
set_ticker(self.parent_Dialog.repeat_rate)
} else if &y > self.ty + self.th then {
self.going_up := &null
if \self.which_down & /self.ticker_rate then
set_ticker(self.parent_Dialog.repeat_rate)
} else {
l := (&y - self.ty) / self.line_height + self$get_line()
l >:= self$get_last_line()
self.which_down := l
stop_ticker()
}
#
# Refresh if line changed
#
if old_down ~=== self.which_down then
self$refresh(1)
}
end
method tick()
if \going_up then {
self.which_down := self$get_line() - 1
self.which_down <:= 1
(\self.vsb)$set_value(self.vsb$get_value() - 1)
}
else {
self.which_down := self$get_last_line() + 1
self.which_down >:= *self.contents
(\self.vsb)$set_value(self.vsb$get_value() + 1)
}
self$refresh(1)
end
method handle_release(e)
if \self.is_held then {
#
# Mouse released after being held down. Clear flag
#
self.is_held := &null
stop_ticker()
#
# Clear flag, refresh, return event
#
self.checked := list(*self.contents)
self.checked[\self.which_down] := 1
self.prev_down := self.which_down
self.which_down := &null
self$refresh(1)
return _Event(e, self, 1)
}
end
method handle_event(e)
if E := (\self.vsb)$handle_event(e) then {
#
# Handled by VSB; amend line number and refresh contents
#
self$refresh()
}
else if E := (\self.hsb)$handle_event(e) then {
#
# Handled by HSB; amend left offset and refresh
#
self$refresh()
}
else if integer(e) = (&lpress | &rpress | &mpress) then
return handle_press(e)
else if integer(e) = (&ldrag | &rdrag | &mdrag) then
return handle_drag(e)
else if integer(e) = (&lrelease | &rrelease | &mrelease) then
return 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)
}
}
end
method get_left_pos()
return (self.tx - (\self.hsb)$get_value()) | self.tx
end
method get_line()
return ((\self.vsb)$get_value() + 1) | 1
end
method get_last_line()
local nlines
nlines := (\self.vsb)$get_page_size() | *self.contents
return self$get_line() + nlines - 1
end
#
# Present page size
#
method get_page_size()
return (\self.vsb)$get_page_size() | *self.contents
end
#
# Goto line, pos
#
method goto_pos(x, y)
(\self.vsb)$set_value(\x - 1)
(\self.hsb)$set_value(\y)
self$redisplay()
end
method compute_and_redisplay()
if \ (\self.parent_Dialog).is_open then
self$set_internal_fields()
self$redisplay()
end
method resize()
self$Component.resize()
self$set_internal_fields()
end
method get_line_height()
return WAttrib(self.cwin, "fheight")
end
method get_max_width()
local mw, s
mw := 0
every s := !self.contents do
mw <:= TextWidth(self.cwin, s)
return mw
end
#
# Called on resize, buttons resized, or contents amended
#
method set_internal_fields()
local max_th, max_tw, min_th, min_tw, need_vsb, need_hsb, new_vsb, new_hsb
self.line_height := get_line_height()
#
# Top left of text region
#
self.tx := self.x + TX_PADDING
self.ty := self.y + TY_PADDING
#
# Initialize left_pos field, clear optimizing flags used
# in display.
#
self.last_refresh_x := self.last_refresh_y := &null
#
# Compute maximum width
#
self.max_width := get_max_width()
#
# Compute max/min heights/widths for text; max if no scroll bar
# needed; min otherwise.
#
max_th := self.h - 2 * TY_PADDING
max_tw := self.w - 2 * TX_PADDING
min_th := max_th - SB_SIZE
min_tw := max_tw - SB_SIZE
#
# Set flags indicating whether scroll bars needed. 0 => definitely not
# 1 => yes if opposite scroll bar needed; 2 => definitely yes.
#
if min_th / self.line_height >= *self.contents then
need_vsb := 0
else if max_th / self.line_height >= *self.contents then
need_vsb := 1
else
need_vsb := 2
if min_tw >= self.max_width then
need_hsb := 0
else if max_tw >= self.max_width then
need_hsb := 1
else
need_hsb := 2
#
# Case analysis on flags to set up correct scroll bars, text width
# and height fields.
#
if (need_vsb < 2) & (need_hsb < 2) then {
#
# No scroll bars.
#
self.th := max_th
self.tw := max_tw
(\self.vsb)$finally()
(\self.hsb)$finally()
self.vsb := self.hsb := &null
} else if (need_hsb + need_vsb > 2) then {
#
# Two scroll bars.
#
if /self.vsb := ScrollBar() then
new_vsb := 1
if /self.hsb := ScrollBar() then {
self.hsb$set_is_horizontal()
new_hsb := 1
}
self.th := min_th
self.tw := min_tw
self.vsb$set_pos(self.w - SB_SIZE - BORDER_WIDTH, BORDER_WIDTH)
self.vsb$set_size(SB_SIZE, self.h - SB_SIZE - BORDER_WIDTH * 2)
self.hsb$set_pos(BORDER_WIDTH, self.h - SB_SIZE - BORDER_WIDTH)
self.hsb$set_size(self.w - SB_SIZE - BORDER_WIDTH * 2, SB_SIZE)
} else if (need_hsb = 0) & (need_vsb = 2) then {
#
# One vertical scroll bar.
#
if /self.vsb := ScrollBar() then
new_vsb := 1
(\self.hsb)$finally()
self.hsb := &null
self.th := max_th
self.tw := min_tw
self.vsb$set_pos(self.w - SB_SIZE - BORDER_WIDTH, BORDER_WIDTH)
self.vsb$set_size(SB_SIZE, self.h - BORDER_WIDTH * 2)
} else if (need_hsb = 2) & (need_vsb = 0) then {
#
# One horizontal scroll bar.
#
if /self.hsb := ScrollBar() then {
self.hsb$set_is_horizontal()
new_hsb := 1
}
(\self.vsb)$finally()
self.vsb := &null
self.th := min_th
self.tw := max_tw
self.hsb$set_pos(BORDER_WIDTH, self.h - SB_SIZE - BORDER_WIDTH)
self.hsb$set_size(self.w - BORDER_WIDTH * 2, SB_SIZE)
}
#
# Initialize scroll bars.
#
if \self.vsb then {
self.vsb$set_page_size(self.th / self.line_height)
self.vsb$set_total_size(*self.contents)
if \new_vsb then {
self.vsb$set_increment_size(1)
self.vsb$set_value(0)
self.vsb$final_setup(self$get_parent_Dialog(), self)
self.vsb$resize()
self.vsb$firstly()
} else
self.vsb$resize()
}
if \self.hsb then {
self.hsb$set_page_size(self.tw)
self.hsb$set_total_size(self.max_width)
if \new_hsb then {
self.hsb$set_increment_size(TextWidth(self.cwin, "m"))
self.hsb$set_value(0)
self.hsb$final_setup(self$get_parent_Dialog(), self)
self.hsb$resize()
self.hsb$firstly()
} else
self.hsb$resize()
}
end
#
# Re-draw the text area. Use double-buffering to avoid flicker.
#
method refresh(redraw)
local line, left_pos
line := self$get_line()
left_pos := self$get_left_pos()
#
# Do nothing unless have to
#
if /redraw & (\self.last_refresh_x = line) & (\self.last_refresh_y = left_pos) then
return
#
# Save present co-ordinates
#
self.last_refresh_x := line
self.last_refresh_y := left_pos
self$text_area_to_buffer()
self$do_shading(self.cbwin)
#
# Copy buffer to window
#
CopyArea(self.cbwin, self.cwin, self.tx, self.ty, self.tw, self.th, self.tx, self.ty)
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()
(\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)
end
##
# This method is overridden by the subclass to draw the given
# line at the given position
# @param N The object to be drawn
# @param left_pos The left position it should be drawn at
# @param yp The y position it should be drawn at
# @param i The index into the data corresponding to N
#
method draw(N, left_pos, yp, i)
error("draw() must be overridden in subclass")
end
method text_area_to_buffer()
local line, left_pos, nlines, yp, l, i
line := self$get_line()
left_pos := self$get_left_pos()
EraseRectangle(self.cbwin, self.tx, self.ty, self.tw, self.th)
#
# Number of lines to draw
#
nlines := (\self.vsb)$get_page_size() | *self.contents
Clip(self.cbwin, self.tx, self.ty, self.tw, self.th)
rev := Clone(self.cbwin, "drawop=reverse")
yp := self.ty + self.line_height / 2
#
# Write the lines
#
every l := self.contents[i := line to line + nlines - 1] do {
draw(l, left_pos, yp, i)
yp +:= self.line_height
}
Uncouple(rev)
rev := &null
Clip(self.cbwin)
return
end
#
# method added to support tabs
# return the line after the "\t" are replaced with the correct
# amount of spaces
#
method actual_line(tab_line)
local posx, sblnks, result_line, x12
posx := 0
sblnks := 0
result_line := tab_line
tab_line ? {
while x12 := move(1) do {
if x12 == "\t" then {
posx := posx + 1
result_line := handle_tab_texts(posx,result_line)
sblnks := sblnks + (8 - (posx-1)%8)
posx := posx + (8 - (posx-1)%8) - 1
}
else
posx := posx + 1
}
}
return result_line
end
#
# helper method for actual_line
#
method handle_tab_texts(posx,result_line)
local tablen
tablen := repl(" ",8 - (posx-1)%8) | " "
return result_line[1:posx] || tablen || result_line[posx+1:0]
end
initially(argv[])
self$Component.initially()
self$set_accepts_tab_focus()
self.checked := []
if *argv > 0 then set_fields(argv)
end
This page produced by UniDoc on 2021/04/15 @ 23:59:43.