# $Id: _table.icn,v 1.1 2003-05-31 06:09:04 jeffery Exp $
##
# This class displays a table, the columns of which are set up
# using TableColumns.
#
class Table : Component(
contents,
max_width,
head_x,
head_y,
head_h,
columns,
tx,
ty,
tw,
th,
hsb,
vsb,
last_refresh_x,
last_refresh_y,
line_height,
checked,
select_one,
select_many,
prev_down,
which_down,
is_held,
going_up,
highlight_lst,
highlight_lst2, # temporary quick fix for 2 colors
rchecked #swj
)
##
# Set the height of the buttons at the top in pixels. If not
# invoked, a sensible default will be used.
# @param x The height
#
method set_button_group_height(x)
return self.head_h := x
end
method get_contents()
return self.contents
end
method set_checked(l)
return self.checked := l
end
method get_checked()
return self.checked
end
#
# Present page size
#
method get_page_size()
return (\self.vsb)$get_page_size() | *self.contents
end
##
# Move to the given position. Either parameter may be omitted.
# @param x The row to move to
# @param y The left offset to move to
#
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
##
# Set the contents of the table. The parameter should be a
# two dimensional list. Each element of the list should
# correspond to one row of the table.
# @param x The contents
#
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
method clear_selections()
self.checked := list(*contents)
redisplay()
return
end
##
# Add the given TableColumn to the Table.
# @param c The column to add.
#
method add(c)
put(self.columns, c)
c$set_parent_table(self)
end
method final_setup(x, y)
self$Component.final_setup(x, y)
every (!self.columns)$final_setup(x, self)
end
##
# Configure the table so that one row of the table may be highlighted.
#
method set_select_one()
self.select_one := 1
end
##
# Configure the table so that several rows of the table may be highlighted.
#
method set_select_many()
self.select_many := 1
end
method finally()
self$Component.finally()
every (!self.columns)$finally()
(\self.vsb)$finally()
(\self.hsb)$finally()
self.vsb := self.hsb := &null
end
#
# Return item currently under the clicked cursor
#
method get_which_down()
return \self.which_down
end
#
# Return row currently under the clicked cursor
#
method row_get_which_down()
return self.contents[\self.which_down]
end
#
# Return item currently under the clicked cursor
#
method get_prev_down()
return \self.prev_down
end
#
# Return row currently under the clicked cursor
#
method row_get_prev_down()
return self.contents[\self.prev_down]
end
#
# Return a list of items highlighted
#
##
# Return a list of rows selected
# @return A list of rows currently selected
#
method get_selections()
r := []
every i := 1 to *self.checked do
if \self.checked[i] then
put(r, i)
return r
end
method get_right_selections() #swj
return self.rchecked
end
##
# Set the current selections to the list l, which is a list of
# row numbers.
# @param l The list of item numbers.
#
method set_selections(l)
self.checked := list(*self.contents)
every self.checked[!l] := 1
redisplay()
end
method get_last_line()
nlines := (\self.vsb)$get_page_size() | *self.contents
return self$get_line() + nlines - 1
end
method handle_press(e)
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()
nlines := (\self.vsb)$get_page_size() | *self.contents
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 handle_rpress(e)
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()
nlines := (\self.vsb)$get_page_size() | *self.contents
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.
#
# self.prev_down := self.which_down
# self.which_down := &null
# self$refresh(1)
self.rchecked := 0
return _Event(e, self, 0)
} else {
#
# Normal click down
#
self.is_held := 1
self.rchecked := 1
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.rchecked := self.which_down
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
#
# Mouse drag - save present marked line
#
old_down := self.which_down
if &y < self.ty then {
self.going_up := 1
if /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.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.rchecked := self.which_down
self.which_down := &null
self$refresh(1)
return _Event(e, self, 1)
}
end
method handle_key_page_up(e)
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)
if i := (\self.vsb)$get_value() then {
self.vsb$set_value(i + self.vsb.page_size)
self$refresh()
}
end
method handle_key_up(e)
if i := (\self.vsb)$get_value() then {
self.vsb$set_value(i - 1)
self$refresh()
}
end
method handle_key_down(e)
if i := (\self.vsb)$get_value() then {
self.vsb$set_value(i + 1)
self$refresh()
}
end
method handle_key_left(e)
if i := (\self.hsb)$get_value() then {
self.hsb$set_value(i - self.hsb.increment_size)
self$scroll_buttons()
self$refresh()
}
end
method handle_key_right(e)
if i := (\self.hsb)$get_value() then {
self.hsb$set_value(i + self.hsb.increment_size)
self$scroll_buttons()
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 then
self$refresh()
else if e$get_component() === self.hsb then {
self$scroll_buttons()
self$refresh()
}
end
method handle_event(e)
local old_down, l
if E := (!self.columns)$handle_event(e) & E$get_code() > 0 then
return E
else 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$scroll_buttons()
self$refresh()
} else return if integer(e) = (&lpress | &rpress | &mpress) then
handle_press(e)
else return if integer(e) = (&rpress) then # swj
handle_rpress(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)
}
}
end
method resize()
if /self.contents then
error("no contents specified")
/self.head_h := WAttrib(self.cwin, "fheight") + 2 * DEFAULT_TEXT_Y_SURROUND
self$Component.resize()
self$set_internal_fields()
end
method scroll_buttons()
i := self$get_left_pos()
every b := !self.columns do {
b$set_pos(i - self.x, self.head_y - self.y)
b$resize()
i +:= b.w
}
end
#
# Called on resize, buttons resized, or contents amended
#
method set_internal_fields()
every (!self.columns)$check_width()
self.line_height := WAttrib(self.cwin, "fheight")
#
# Top of button region
#
self.head_y := self.y + BORDER_WIDTH
#
# Top left of text region
#
self.tx := self.x + TX_PADDING
self.ty := self.head_y + self.head_h + 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 := 0
every self.max_width +:= (!self.columns).column_width
#
# Compute max/min heights/widths for text; max if no scroll bar
# needed; min otherwise. Deduct an extra border from max_th to
# be even within the bottom of the whole and the bottom of the header.
#
max_th := self.h - self.head_h - 2 * TY_PADDING - 2 * BORDER_WIDTH
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.
#
#
# 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()
}
#
# Compute button x, y positions
#
self$scroll_buttons()
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 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$button_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
#
# Re-draw the text area. Use double-buffering to avoid flicker.
#
method refresh(redraw)
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
if \redraw | not(\self.last_refresh_y = left_pos) then {
self$button_area_to_buffer()
CopyArea(self.cbwin, self.cwin, self.tx, self.head_y, self.tw, self.head_h, self.tx, self.head_y)
}
#
# Save present co-ordinates
#
self.last_refresh_x := line
self.last_refresh_y := left_pos
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 button_area_to_buffer()
EraseRectangle(self.cbwin, self.tx, self.head_y, self.tw, self.head_h)
#
# Draw buttons into buffer
#
every (!self.columns)$display(1)
end
method text_area_to_buffer()
local rev,h_set,h_set2
if *\highlight_lst > 0 then
h_set := set(highlight_lst)
else
h_set := set()
if *\highlight_lst2 > 0 then
h_set2 := set(highlight_lst2)
else
h_set2 := set()
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
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 {
if member(h_set, i) then
WAttrib(self.cbwin, "fg=red")
else if member(h_set2, i) then
WAttrib(self.cbwin, "fg=blue") # swj
every j := 1 to *self.columns do {
x1 := self.columns[j].x + DEFAULT_TEXT_X_SURROUND
w1 := self.columns[j].w - 2 * DEFAULT_TEXT_X_SURROUND
clip_x1 := x1
clip_x1 <:= self.tx
clip_x2 := x1 + w1
clip_x2 >:= self.tx + self.tw
Clip(self.cbwin, clip_x1, self.ty, clip_x2 - clip_x1, self.th)
case self.columns[j].internal_alignment of {
"r" : right_string(self.cbwin, x1 + w1, yp, l[j])
"c" : center_string(self.cbwin, x1 + w1 / 2, yp, l[j])
"l" : left_string(self.cbwin, x1, yp, l[j])
}
Clip(self.cbwin)
}
WAttrib(self.cbwin, "fg=black")
if \self.checked[i] | i = \self.which_down then {
if member(h_set,i) then
WAttrib(rev, "fg=pale cyan") # swj
else if member(h_set2,i) then
WAttrib(rev, "fg=pale cyan") # swj
FillRectangle(rev, self.tx, yp - self.line_height / 2, self.tw, self.line_height)
}
yp +:= self.line_height
}
Uncouple(rev)
end
initially(argv[])
self$Component.initially()
self.columns := []
self$set_accepts_tab_focus()
self.checked := []
self.rchecked := 0
highlight_lst := []
highlight_lst2 := []
if *argv > 0 then set_fields(argv)
end
This page produced by UniDoc on 2021/04/15 @ 23:59:43.