Copyright (C) 1994, Digital Equipment Corp.
<* PRAGMA LL *>A
ListVBT defines a VBT class for displaying a list (or
table) of items. Each item is in a {\em cell}. All cells are
the same size. They are displayed in a single vertical
column, with a scrollbar.
The ListVBT itself deals with the details of being a VBT,
maintains a table that maps a cell-number to a cell-value, and
maintains the {\em selection}, a distinguished subset of the
cells. It uses subsidiary objects to handle the details of
what cells look like on the screen (Painter), and how the
list responds to mouse clicks (Selector).
This interface contains basic versions of each of the subsidiary objects:
\begin{itemize}
\item TextPainter, which treats cells' values as TEXT and
paints them.
\item UniSelector, which maintains at most one selected
cell, adjusted by mouse clicks.
\item MultiSelector, which uses mouse clicks for selection,
but permits multiple cells to be selected.
\end{itemize}
The client can subclass these, or provide entirely different ones.
A client that wishes to take actions in response to mouse clicks
should subclass a Selector. Similarly, a client that wishes to
display objects other than text strings should subclass Painter.
\subsubsection{Locking levels}
ListVBT is internally synchronized; it can safely be called
from multiple threads. All ListVBT.T methods have LL.sup <
list. In addition, VBT.mu < list for any list of type
ListVBT.T.
VBT methods call Selector methods with LL.sup = VBT.mu.
Selector methods are permitted to call ListVBT.T methods.
ListVBT.T methods call Painter methods with the ListVBT.T's
internal mutex held. Painter methods must not call any of the
ListVBT.T methods; their locking level is such that LL.sup =
list.
The TextPainter class uses its own internal lock for font
information; \linebreak TextPainter.setFont(v,font) has LL.sup < v.
\subsubsection{The type {ListVBT.T}}
INTERFACEListVBT ; IMPORT Font, PaintOp, Rect, VBT; TYPE Cell = INTEGER;
The number of a cell; the first cell-number is 0.
TYPE
T <: Public;
Private <: VBT.Split;
Public = Private OBJECT
painter : Painter := NIL;
selector: Selector := NIL;
METHODS
init (colors: PaintOp.ColorQuad): T;
setValue (this: Cell; value: REFANY);
getValue (this: Cell): REFANY;
count (): CARDINAL;
insertCells (at: Cell; n: CARDINAL);
removeCells (at: Cell; n: CARDINAL);
selectNone ();
selectOnly (this: Cell);
select (this: Cell; selected: BOOLEAN);
isSelected (this: Cell): BOOLEAN;
getAllSelected (): REF ARRAY OF Cell;
getFirstSelected (VAR this: Cell): BOOLEAN;
scrollTo (this: Cell);
scrollToShow (this: Cell);
reportVisible (first: Cell; num: CARDINAL);
END;
In the following descriptions, v is an object of type
ListVBT.T, and a value n is said to be {\it in range} if
\medskip {\display {\tt 0 $\leq$ n < v.count() }}
\medskip v.painter is the list's painter; the client may read but not
assign to this field, although the client may provide a value
at allocation time. If the actual painter has methods
allowing it to be modified, the client is welcome to call
them, although the client and painter are then responsible for
provoking any necessary repaints.
v.selector is the list's selector; client may read but not
assign to this field, although the client may provide a value
at allocation time. If the actual selector has methods
allowing it to be modified, the client is welcome to call
them, although the client and selector are then responsible
for any necessary adjustments to the set of selected cells.
The call v.init(colors) initializes v as a ListVBT and
returns v. It must be called before any other method. colors
is passed intact to the scroller; colors.fg is used for a bar
that separates the cells from the scroller. If v.painter = NIL
when this method is called, init will allocate and initialize a
TextPainter. If v.selector = NIL, init will allocate and
initialize a UniSelector. Neither the painter nor the selector
need have been initialized before this method is called. The list
initially has no cells (and no selection).
In the call v.setValue(this,value), if this is in range,
then record value as the value of the cell this; otherwise
do nothing.
In the call v.getValue(this), if this is in range,
then return the previously recorded value of the cell this;
otherwise return NIL.
The call v.count() returns the number of cells.
The call v.insertCells(at,n) inserts n cells, starting at
MAX (0, MIN (at, v.count()))
Previously existing cells at and beyond at are renumbered
appropriately, and selections are relocated appropriately.
The VBT will be repainted in due course. The new cells'
values are all NIL, and they are not selected.
The call v.removeCells(at, n) removes all cells in the range
[MAX (0, MIN (at, v.count ())) ..
-1 + MIN (at + n, v.count ())]
Subsequent cells are renumbered appropriately. The VBT will
be repainted in due course.
The call v.selectNone() makes the set of selected cells be
empty.
In the call v.selectOnly(this), if this is in range, make
the set of selected cells be exactly this; otherwise make
the list of selected cells be empty. Equivalent to
v.selectNone(); v.select(this,TRUE)
In the call v.select(this,selected), if this is in range
and selected is TRUE, add this to the set of selected
cells (without complaint if it's already selected); otherwise
if this is in range and selected is FALSE, remove it
from the set of selected cells (again without complaint).
The VBT will be repainted as necessary in due course.
The call v.isSelected(this) returns TRUE if this is
in range and is a selected cell; otherwise it returns FALSE.
The call v.getAllSelected() returns the set of selected cells.
If there are none, it returns a non-NIL REF to an array of length
0.
The call v.getFirstSelected(this) assigns to this the
lowest-numbered selected cell and returns TRUE; if there are
no selected cells, it returns FALSE.
The call v.scrollTo(this) adjusts the list's scrolling
position to place
MAX (0, MIN (this, v.count () - 1) )
at the top of v's domain.
The call v.scrollToShow(this) adjusts the list's scrolling
position to make this visible.
The ListVBT will call v.reportVisible(first, num) whenever
the set of visible cells changes (either because of scrolling
or because of reshaping). (A cell is ``visible'' if it
is within the domain of the ListVBT; it may not be visible
to the user if other windows obscure the ListVBT.) The
argument first is the index of the first visible cell, and
num is the number of visible cells. The default for this
method is a no-op; override it if you need the information
it provides. The locking level of the method is LL.sup = v
(that is, the ListVBT itself is locked when the method is called,
so the method mustn't operate on v).
\subsubsection{The Painter}
Here is the definition of a Painter. In the comments about
its methods, v is the VBT in which the painting is to take
place; it is the ListVBT.T or a subtype of it. Recall
that LL.sup = list for all methods, other than init.
TYPE
Painter = OBJECT
METHODS
init (): Painter;
height (v: VBT.T): INTEGER;
paint (v : VBT.T;
r : Rect.T;
value : REFANY;
index : CARDINAL;
selected: BOOLEAN;
bad : Rect.T );
select (v : VBT.T;
r : Rect.T;
value : REFANY;
index : CARDINAL;
selected: BOOLEAN );
erase (v: VBT.T; r: Rect.T);
END;
The call p.init() initializes p as a Painter and returns
p.
The call p.height(v) returns the pixel height of each cell
if painted in v. The list caches the result of this call,
so it needn't be very efficient. It is called only when the
list has a non-empty domain. It gets re-evaluated whenever
the list's screen changes.
The call p.paint(v, r, value, index, select, bad) paints the cell
with the given index and value in the given rectangle (whose height
will equal that returned by p.height(), and some part of which will be
visible). If selected is TRUE, highlight the painted cell to indicate
that it is in the set of selected cells. bad is the subset
of r that actually needs to be painted; bad is wholly
contained in r.
The call p.select(v, r, value, index, selected) changes the
highlight of the cell with the given index and value,
according to selected, to show whether it is
in the set of selected cells. The cell has previously been
painted; its selection state has indeed changed. It's OK for
this method to be identical to paint, but it might be more
efficient or cause less flicker, e.g. by just inverting r.
The call p.erase(v, r) paints the given rectangle to show
that it contains no cells. Typically, this just fills it with
the background color used when painting cells.
\subsubsection{TextPainter}
Perhaps the most common type of Painter is a TextPainter.
It displays cells whose values are text strings. Here is its
public definition:
TYPE
TextPainter <: TextPainterPublic;
TextPainterPublic =
Painter OBJECT
METHODS
init (bg := PaintOp.Bg;
fg := PaintOp.Fg;
hiliteBg := PaintOp.Fg;
hiliteFg := PaintOp.Bg;
font := Font.BuiltIn): TextPainter;
setFont (v: VBT.T; font: Font.T); <* LL.sup < v *>
END;
The call p.init(...) initializes p as a TextPainter and
returns p. Unselected cells are painted with fg text on
bg; selected cells are painted with hiliteFg text on
hiliteBg; erased areas are painted with bg. Text is drawn
using font.
After the call p.setFont(v, font), the TextPainter uses
font for subsequent painting of values; the call also marks
v for redisplay. v should be the relevant ListVBT.T.
\subsubsection{The Selector}
Here is the definition of Selector. Recall that LL.sup =
VBT.mu for all methods other than init.
TYPE
Selector =
OBJECT
METHODS
init (v: T): Selector;
insideClick (READONLY cd: VBT.MouseRec; this: Cell);
outsideClick (READONLY cd: VBT.MouseRec);
insideDrag (READONLY cd: VBT.PositionRec; this: Cell);
outsideDrag (READONLY cd: VBT.PositionRec);
END;
The call s.init(v) initializes s as a Selector and
returns s. The ListVBT v need not have been initialized
before this method is called.
The call s.insideClick(cd, this) is called on a FirstDown mouse
click inside the cell, or on any mouse click inside the cell while
we have the mouse focus. On any click other than LastUp, the
list itself has set a cage so that it receives position reports
during subsequent drags.
The call s.outsideClick(cd) is called when there is a FirstDown
click in the ListVBT that is not in a cell, or on any mouse click
not in a cell while we have the mouse focus. On any click other
than LastUp, the list itself has set a cage so that it receives
position reports during subsequent drags.
The call s.insideDrag(cd) is called if the list has received a
FirstDown click and a subsequent position report with the mouse
not in any cell. The list itself has set a cage so that it
receives further position reports.
The call s.outsideDrag(cd) is called if the list has the mouse
focus and receives a subsequent position report with the mouse in
this cell. The list itself has set a cage so that it receives
further position reports.
\subsubsection{UniSelector and MultiSelector}
One common class of Selector is a UniSelector. It
maintains the invariant that there is at most one selected
cell. On an insideClick firstDown, or an insideDrag, it
removes any previous selection and then selects this cell. Its
other methods do nothing. Here is its declaration:
TYPE UniSelector <: Selector;The other common class of
Selector is MultiSelector. It
permits multiple cells to be selected. On an insideClick
firstDown, it remembers this cell as the {\em anchor}; if this is
not a shift-click, it calls selectNone and inverts the
selection state of this cell. On an insideDrag, it makes the
selection state of all cells between this cell and the anchor
be the same as that of the anchor. Here is its
declaration:
TYPE MultiSelector <: Selector; END ListVBT.