Quelle browse.gi
Sprache: unbekannt
|
|
##############################################################################
##
#W browse.gi GAP 4 package `browse' Thomas Breuer
##
#############################################################################
##
DeclareUserPreference( rec(
name:= "EnableMouseEvents",
description:= [
"This user preference defines whether mouse events are enabled by default \
in visual mode (value 'true') or not (value 'false', this is the default). \
During the ⪆ session, \
the value can be changed using 'NCurses.UseMouse'. \
Inside browse applications based on 'NCurses.BrowseGeneric' \
or 'NCurses.Select', \
the value can be toggled usually by hitting the 'M' key." ],
default:= false,
values:= [ true, false ],
multi:= false,
) );
if UserPreference( "Browse", "EnableMouseEvents" ) = true then
NCurses.UseMouse( true );
fi;
#############################################################################
##
#V BrowseData
##
BrowseData.actions:= rec();
BrowseData.defaults:= rec(
work:= rec(
# global positioning
windowParameters:= [ 0, 0, 0, 0 ], # full window
minyx:= [ 0, 0 ], # is replaced by functions below
align:= "", # default: right and vertically centered
# table data: five matrices, four separators
# (no default for `main', the sixth matrix)
header:= [],
footer:= [],
corner:= [],
labelsCol:= [],
labelsRow:= [],
sepLabelsCol:= "",
sepLabelsRow:= "",
sepCol:= " ",
sepRow:= "",
cacheEntries:= false,
cornerFormatted:= [],
labelsColFormatted:= [],
labelsRowFormatted:= [],
mainFormatted:= [],
Main:= false,
heightRow:= [],
widthCol:= [],
heightLabelsCol:= [],
widthLabelsRow:= [],
# (at most) mode dependent lengths
headerLength:= rec(),
footerLength:= rec(),
# how to fill missing entries
emptyCell:= "", # table cell data object
# for categories feature
sepCategories:= "-",
startCollapsedCategory:= [ [ NCurses.attrs.BOLD, true, "> " ] ],
startExpandedCategory:= [ [ NCurses.attrs.BOLD, true, "* " ] ],
# how to mark a selected cell/row/column
startSelect:= [ NCurses.attrs.STANDOUT, true ],
# mode info (will be filled below)
availableModes:= [],
# customized modes
customizedModes:= rec(),
Click:= rec(),
),
dynamic:= rec(
# for the sort feature, will be filled with the table
indexRow:= [],
indexCol:= [],
# topleft is [i, j, k, l]: entry[i][j] blockposition[k][l]
topleft := [1, 1, 1, 1],
isCollapsedCol:= [],
isCollapsedRow:= [],
isRejectedCol:= [],
isRejectedRow:= [],
isRejectedLabelsCol:= [],
isRejectedLabelsRow:= [],
# mode info (will be filled as soon as the `browse' mode is defined)
activeModes:= [],
# for selecting a cell or row or column or category
selectedEntry:= [ 0, 0 ],
selectedCategory:= [ 0, 0 ],
#T not used anymore, but some applications may rely on its presence
useMouse:= false,
# for the search feature
searchString:= "",
searchParameters:= [
[ "case sensitive", [ "yes", "no" ], 2 ],
[ "mode", [ "substring", "whole entry" ], 1 ],
[ "search for", [ "any substring", "word", "prefix", "suffix" ],
1, paras -> ForAny( paras, x -> x[1] = "mode" and x[3] = 1 ) ],
[ "compare with",
[ "\"<\"", "\"<=\"", "\"=\"", "\">=\"", "\">\"", "\"<>\"" ], 3,
paras -> ForAny( paras, x -> x[1] = "mode" and x[3] = 2 ) ],
#T new option: "with wildcards *,?"
[ "search", [ "forwards", "backwards" ], 1 ],
[ "search", [ "row by row", "column by column" ], 1 ],
[ "wrap around", [ "yes", "no" ], 1 ],
[ "negate", [ "yes", "no" ], 2 ],
],
# for the sort feature
sortParametersForRows:= [],
sortParametersForColumns:= [],
sortParametersForRowsDefault:= [
[ "direction", [ "ascending", "descending" ], 1 ],
[ "case sensitive", [ "yes", "no" ], 1 ],
],
sortParametersForColumnsDefault:= [
[ "direction", [ "ascending", "descending" ], 1 ],
[ "case sensitive", [ "yes", "no" ], 2 ],
[ "hide on categorizing", [ "yes", "no" ], 1 ],
[ "add counter on categorizing", [ "yes", "no" ], 2 ],
[ "split rows on categorizing", [ "yes", "no" ], 2 ],
],
#T other parameters: as strings or numbers or via function,
#T secondary sort rows/columns
sortFunctionsForRows:= [],
sortFunctionsForColumns:= [],
sortFunctionForTableDefault:= \<,
sortFunctionForRowsDefault:= \<,
sortFunctionForColumnsDefault:= \<,
# for the categorizing feature
categories:= [ [], [], [] ],
# for the filtering feature
filterParameters:= [
[ "case sensitive", [ "yes", "no" ], 2 ],
[ "mode", [ "substring", "whole entry" ], 1 ],
[ "search for", [ "any substring", "word", "prefix", "suffix" ],
1, paras -> ForAny( paras, x -> x[1] = "mode" and x[3] = 1 ) ],
[ "compare with",
[ "\"<\"", "\"<=\"", "\"=\"", "\">=\"", "\">\"", "\"<>\"" ], 3,
paras -> ForAny( paras, x -> x[1] = "mode" and x[3] = 2 ) ],
#T new option: "with wildcards *,?"
[ "negate", [ "yes", "no" ], 2 ],
],
# for the replay feature
replayDefaults:= rec(
steps:= [],
position:= 1,
replayInterval:= 200,
quiet:= false,
),
# for the logging feature
log:= [],
),
);
#############################################################################
##
#F BrowseData.MinimalWindowHeight( <t> )
#F BrowseData.MinimalWindowWidth( <t> )
##
BrowseData.MinimalWindowHeight:= function( t )
local height;
height:= BrowseData.HeightLabelsColTable( t ) +
BrowseData.HeightRow( t, 1 ) +
BrowseData.HeightRow( t, 2 );
if IsList( t.work.header ) then
height:= height + Length( t.work.header );
fi;
if IsList( t.work.footer ) then
height:= height + Length( t.work.footer );
fi;
return height;
end;
BrowseData.MinimalWindowWidth:= function( t )
return BrowseData.WidthLabelsRowTable( t ) +
BrowseData.WidthCol( t, 1 ) +
BrowseData.WidthCol( t, 2 );
end;
BrowseData.defaults.work.minyx:= [ BrowseData.MinimalWindowHeight,
BrowseData.MinimalWindowWidth ];
#############################################################################
##
#F BrowseData.IsBrowseTableCellData( <obj> )
##
## <#GAPDoc Label="IsBrowseTableCellData_man">
## <ManSection>
## <Func Name="BrowseData.IsBrowseTableCellData" Arg="obj"/>
##
## <Returns>
## <K>true</K> if the argument is a list or a record in a supported format.
## </Returns>
##
## <Description>
## A <E>table cell data object</E> describes the input data for the contents
## of a cell in a browse table.
## It is represented by either an attribute line
## (see <Ref Func="NCurses.IsAttributeLine"/>),
## for cells of height one, or a list of attribute lines
## or a record with the components <C>rows</C>, a list of attribute lines,
## and optionally <C>align</C>, a substring of <C>"bclt"</C>,
## which describes the alignment of the attribute lines in the table cell
## -- bottom, horizontally centered, left, and top alignment;
## the default is right and vertically centered alignment.
## (Note that the height of a table row and the width of a table column
## can be larger than the height and width of an individual cell.)
## <P/>
## <Example><![CDATA[
## gap> BrowseData.IsBrowseTableCellData( "abc" );
## true
## gap> BrowseData.IsBrowseTableCellData( [ "abc", "def" ] );
## true
## gap> BrowseData.IsBrowseTableCellData( rec( rows:= [ "ab", "cd" ],
## > align:= "tl" ) );
## true
## gap> BrowseData.IsBrowseTableCellData( "" );
## true
## gap> BrowseData.IsBrowseTableCellData( [] );
## true
## ]]></Example>
## <P/>
## The <E>empty string</E> is a table cell data object of height one and
## width zero whereas the <E>empty list</E>
## (which is not in <Ref Func="IsStringRep" BookName="ref"/>)
## is a table cell data object of height zero and width zero.
## </Description>
## </ManSection>
## <#/GAPDoc>
##
BrowseData.IsBrowseTableCellData:=
obj -> NCurses.IsAttributeLine( obj ) or
( IsDenseList( obj ) and ForAll( obj, NCurses.IsAttributeLine ) ) or
( IsRecord( obj ) and IsBound( obj.rows ) and IsDenseList( obj.rows )
and ForAll( obj.rows, NCurses.IsAttributeLine ) );
#############################################################################
##
#F BrowseData.IsModeRecord( <obj> )
##
BrowseData.IsModeRecord:=
obj -> IsRecord( obj ) and
IsBound( obj.name ) and IsString( obj.name ) and
IsBound( obj.flag ) and IsString( obj.flag ) and
IsBound( obj.ShowTables ) and IsFunction( obj.ShowTables ) and
IsBound( obj.actions ) and IsList( obj.actions ) and
ForAll( obj.actions,
x -> IsList( x ) and Length( x ) = 3
and IsList( x[1] )
and ForAll( x[1], y -> IsPosInt( y )
or y in NCurses.mouseEvents )
and IsRecord( x[2] ) and IsBound( x[2].helplines )
and IsList( x[2].helplines )
and ForAll( x[2].helplines, NCurses.IsAttributeLine )
and IsBound( x[2].action ) and IsFunction( x[2].action )
and IsString( x[3] ) and not IsEmpty( x[3] ) );
#############################################################################
##
#F BrowseData.IsBrowseTable( <obj> )
##
## <#GAPDoc Label="IsBrowseTable_man">
## <ManSection>
## <Func Name="BrowseData.IsBrowseTable" Arg="obj"/>
##
## <Returns>
## <K>true</K> if the argument record has the required components
## and is consistent.
## </Returns>
##
## <Description>
## A <E>browse table</E> is a &GAP; record that can be used as the first
## argument of the function <Ref Func="NCurses.BrowseGeneric"/>.
## <P/>
## The supported components of a browse table are <C>work</C> and
## <C>dynamic</C>, their values must be records:
## The components in <C>work</C> describe that part of the data that
## are not likely to depend on user interactions,
## such as the table entries and their heights and widths.
## The components in <C>dynamic</C> describe that part of the data that
## is intended to change with user interactions,
## such as the currently shown top-left entry of the table,
## or the current status w.r.t. sorting.
## For example, suppose you call <Ref Func="NCurses.BrowseGeneric"/> twice
## with the same browse table;
## the second call enters the table in the same status where it was left
## <E>after</E> the first call if the component <C>dynamic</C> is kept,
## whereas one has to reset (which usually simply means to unbind) the
## component <C>dynamic</C> if one wants to start in the same status as
## <E>before</E> the first call.
## <P/>
## The following components are the most important ones for standard browse
## applications.
## All these components belong to the <C>work</C> record.
## For other supported components (of <C>work</C> as well as of
## <C>dynamic</C>) and for the meaning of the term <Q>mode</Q>,
## see Section <Ref Sect="sec:modes"/>.
##
## <List>
## <Mark><C>main</C></Mark>
## <Item>
## is the list of lists of table cell data objects that form the matrix
## to be shown.
## There is no default for this component.
## (It is possible to compute the entries of the main table on demand,
## see the description of the component <C>Main</C>
## in Section <Ref Subsect="BrowseData"/>.
## In this situation, the value of the component <C>main</C> can be an
## empty list.)
## </Item>
## <Mark><C>header</C></Mark>
## <Item>
## describes a header that shall be shown above the column labels.
## The value is either a list of attribute lines (<Q>static header</Q>)
## or a function or a record whose component names are names of
## available modes of the browse table (<Q>dynamic header</Q>).
## In the function case, the function must take the browse table as its
## only argument, and return a list of attribute lines.
## In the record case, the values of the components must be such
## functions.
## It is assumed that the number of these lines depends at most on the
## mode.
## The default is an empty list, i. e., there is no header.
## </Item>
## <Mark><C>footer</C></Mark>
## <Item>
## describes a footer that shall be shown below the table.
## The value is analogous to that of <C>footer</C>.
## The default is an empty list, i. e., there is no footer.
## </Item>
## <Mark><C>labelsRow</C></Mark>
## <Item>
## is a list of row label rows,
## each being a list of table cell data objects.
## These rows are shown to the left of the main table.
## The default is an empty list, i. e., there are no row labels.
## </Item>
## <Mark><C>labelsCol</C></Mark>
## <Item>
## is a list of column information rows,
## each being a list of table cell data objects.
## These rows are shown between the header and the main table.
## The default is an empty list, i. e., there are no column labels.
## </Item>
## <Mark><C>corner</C></Mark>
## <Item>
## is a list of lists of table cell data objects that are printed
## in the upper left corner, i. e.,
## to the left of the column label rows and above the row label columns.
## The default is an empty list.
## </Item>
## <Mark><C>sepRow</C></Mark>
## <Item>
## describes the separators above and below rows of the main table
## and of the row labels table.
## The value is either an attribute line or a (not necessarily dense)
## list of attribute lines.
## In the former case, repetitions of the attribute line are used as
## separators below each row and above the first row of the table;
## in the latter case, repetitions of the entry at the first position
## (if bound) are used above the first row, and repetitions of the last
## bound entry before the <M>(i+2)</M>-th position (if there is such an
## entry at all) are used below the <M>i</M>-th table row.
## The default is an empty string,
## which means that there are no row separators.
## </Item>
## <Mark><C>sepCol</C></Mark>
## <Item>
## describes the separators in front of and behind columns of the main
## table and of the column labels table.
## The format of the value is analogous to that of the component
## <C>sepRow</C>;
## the default is the string <C>" "</C> (whitespace of width one).
## </Item>
## <Mark><C>sepLabelsCol</C></Mark>
## <Item>
## describes the separators above and below rows of the column labels
## table and of the corner table,
## analogously to <C>sepRow</C>.
## The default is an empty string,
## which means that there are no column label separators.
## </Item>
## <Mark><C>sepLabelsRow</C></Mark>
## <Item>
## describes the separators in front of and behind columns of the row
## labels table and of the corner table,
## analogously to <C>sepCol</C>.
## The default is an empty string.
## </Item>
## </List>
##
## We give a few examples of standard applications.
## <P/>
## The first example defines a small browse table by prescribing only the
## component <C>work.main</C>,
## so the defaults for row and column labels (no such labels),
## and for separators are used.
## The table cells are given by plain strings, so they have height one.
## Usually this table will fit on the screen.
## <P/>
## <Example><![CDATA[
## gap> m:= 10;; n:= 5;;
## gap> xpl1:= rec( work:= rec(
## > main:= List( [ 1 .. m ], i -> List( [ 1 .. n ],
## > j -> String( [ i, j ] ) ) ) ) );;
## gap> BrowseData.IsBrowseTable( xpl1 );
## true
## ]]></Example>
## <P/>
## In the second example, also row and column labels appear,
## and different separators are used.
## The table cells have height three.
## Also this table will usually fit on the screen.
## <P/>
## <Example><![CDATA[
## gap> m:= 6;; n:= 5;;
## gap> xpl2:= rec( work:= rec(
## > main:= List( [ 1 .. m ], i -> List( [ 1 .. n ],
## > j -> rec( rows:= List( [ -i*j, i*j*1000+j, i-j ], String ),
## > align:= "c" ) ) ),
## > labelsRow:= List( [ 1 .. m ], i -> [ String( i ) ] ),
## > labelsCol:= [ List( [ 1 .. n ], String ) ],
## > sepRow:= "-",
## > sepCol:= "|",
## > ) );;
## gap> BrowseData.IsBrowseTable( xpl2 );
## true
## ]]></Example>
## <P/>
## The third example additionally has a static header and a dynamic footer,
## and the table cells involve attributes.
## This table will usually not fit on the screen.
## Note that <C>NCurses.attrs.ColorPairs</C> is available only if the
## terminal supports colors, which can be checked using
## <Ref Var="NCurses.attrs.has_colors"/>.
## <P/>
## <Example><![CDATA[
## gap> m:= 30;; n:= 25;;
## gap> xpl3:= rec( work:= rec(
## > header:= [ " Example 3" ],
## > labelsRow:= List( [ 1 .. 30 ], i -> [ String( i ) ] ),
## > sepLabelsRow:= " % ",
## > sepLabelsCol:= "=",
## > sepRow:= "*",
## > sepCol:= " |",
## > footer:= t -> [ Concatenation( "top-left entry is: ",
## > String( t.dynamic.topleft{ [ 1, 2] } ) ) ],
## > ) );;
## gap> if NCurses.attrs.has_colors then
## > xpl3.work.main:= List( [ 1 .. m ], i -> List( [ 1 .. n ],
## > j -> rec( rows:= [ String( -i*j ),
## > [ NCurses.attrs.BOLD, true,
## > NCurses.attrs.ColorPairs[56+1], true,
## > String( i*j*1000+j ),
## > NCurses.attrs.NORMAL, true ],
## > String( i-j ) ],
## > align:= "c" ) ) );
## > xpl3.work.labelsCol:= [ List( [ 1 .. 30 ], i -> [
## > NCurses.attrs.ColorPairs[ 56+4 ], true,
## > String( i ),
## > NCurses.attrs.NORMAL, true ] ) ];
## > else
## > xpl3.work.main:= List( [ 1 .. m ], i -> List( [ 1 .. n ],
## > j -> rec( rows:= [ String( -i*j ),
## > [ NCurses.attrs.BOLD, true,
## > String( i*j*1000+j ),
## > NCurses.attrs.NORMAL, true ],
## > String( i-j ) ],
## > align:= "c" ) ) );
## > xpl3.work.labelsCol:= [ List( [ 1 .. 30 ], i -> [
## > NCurses.attrs.BOLD, true,
## > String( i ),
## > NCurses.attrs.NORMAL, true ] ) ];
## > fi;
## gap> BrowseData.IsBrowseTable( xpl3 );
## true
## ]]></Example>
## <P/>
## The fourth example illustrates that highlighting may not work properly
## for browse tables containing entries whose attributes are not set with
## explicit Boolean values, see <Ref Func="NCurses.IsAttributeLine"/>.
## Call <Ref Func="NCurses.BrowseGeneric"/> with the browse table
## <C>xpl4</C>, and select an entry (or a column or a row):
## Only the middle row of each selected cell will be highlighted,
## because only in this row, the color attribute is switched on with an
## explicit <K>true</K>.
## <P/>
## <Example><![CDATA[
## gap> xpl4:= rec(
## > defc:= NCurses.defaultColors,
## > wd:= Maximum( List( ~.defc, Length ) ),
## > ca:= NCurses.ColorAttr,
## > work:= rec(
## > header:= [ "Examples of NCurses.ColorAttr" ],
## > main:= List( ~.defc, i -> List( ~.defc,
## > j -> [ [ ~.ca( i, j ), String( i, ~.wd ) ], # no true!
## > [ ~.ca( i, j ), true, String( "on", ~.wd ) ],
## > [ ~.ca( i, j ), String( j, ~.wd ) ] ] ) ), # no true!
## > labelsRow:= List( ~.defc, i -> [ String( i ) ] ),
## > labelsCol:= [ List( ~.defc, String ) ],
## > sepRow:= "-",
## > sepCol:= [ " |", "|" ],
## > ) );;
## gap> BrowseData.IsBrowseTable( xpl4 );
## true
## ]]></Example>
## </Description>
## </ManSection>
## <#/GAPDoc>
##
BrowseData.IsBrowseTable:= function( obj )
local result, work, comp, complen, dynamic, l, h, log;
if not IsRecord( obj ) then
Print( "#E <obj> must be a record\n" );
return false;
fi;
result:= true;
if IsBound( obj.work ) then
work:= obj.work;
# windowParameters
if IsBound( work.windowParameters ) then
if not ( IsList( work.windowParameters ) and
Length( work.windowParameters ) = 4 and
ForAll( work.windowParameters,
x -> IsInt( x ) and 0 <= x ) ) then
Print( "#E <obj>.work.windowParameters must be a list ",
"of four nonnegative integers.\n" );
result:= false;
fi;
fi;
# align
if IsBound( work.align ) then
if not ( IsString( work.align ) and
IsSubset( "bclrt", Set( work.align ) ) ) then
Print( "#E <obj>.work.align must be a subset of \"bclrt\".\n" );
result:= false;
fi;
fi;
# header and headerLength, footer and footerLength
for comp in [ "header", "footer" ] do
if IsBound( work.( comp ) ) and
not ( ( IsList( work.( comp ) ) and
ForAll( work.( comp ), NCurses.IsAttributeLine ) ) or
IsFunction( work.( comp ) ) or
( IsRecord( work.( comp ) ) and
ForAll( RecNames( work.( comp ) ),
n -> IsFunction( work.( comp ).( n ) ) ) ) ) then
Print( "#E <obj>.work.", comp,
" must be a list of attribute lines\n",
"#E or a function or a record of functions.\n" );
result:= false;
fi;
complen:= Concatenation( comp, "Length" );
if IsBound( work.( complen ) ) and
not ( IsRecord( work.( complen ) ) and
ForAll( RecNames( work.( complen ) ),
n -> IsInt( work.( complen ).( n ) ) ) ) then
Print( "#E <obj>.work.", complen, " must be a record with ",
"integer values.\n" );
result:= false;
fi;
od;
# labelsCol, labelsRow, corner, main
for comp in [ "labelsCol", "labelsRow", "corner", "main" ] do
if IsBound( work.( comp ) ) and
not ( IsList( work.( comp ) ) and
ForAll( work.( comp ),
x -> IsList( x ) and
ForAll( x,
BrowseData.IsBrowseTableCellData ) ) ) then
Print( "#E <obj>.work.", comp, " must be a list of lists ",
"of table cell data objects.\n" );
result:= false;
fi;
od;
# sepRow, sepCol, sepLabelsCol, sepLabelsRow
for comp in [ "sepRow", "sepCol", "sepLabelsCol", "sepLabelsRow" ] do
if IsBound( work.( comp ) ) and
not ( NCurses.IsAttributeLine( work.( comp ) ) or
( IsList( work.( comp ) ) and
ForAll( work.( comp ), NCurses.IsAttributeLine ) ) ) then
Print( "#E <obj>.work.", comp, " must be an attribute line or ",
"a list of attribute lines.\n" );
result:= false;
fi;
od;
# cacheEntries
if IsBound( work.cacheEntries ) and
not IsBool( work.cacheEntries ) then
Print( "#E <obj>.work.cacheEntries must be a Boolean.\n" );
result:= false;
fi;
# cornerFormatted, labelsColFormatted, labelsRowFormatted, mainFormatted
for comp in [ "cornerFormatted", "labelsColFormatted",
"labelsRowFormatted", "mainFormatted" ] do
if IsBound( work.( comp ) ) and
not ( IsList( work.( comp ) ) and
ForAll( work.( comp ), IsList ) and
ForAll( work.( comp ),
x -> ForAll( x,
y -> NCurses.IsAttributeLine( y ) or
( IsList( y ) and
ForAll( y,
NCurses.IsAttributeLine ) ) ) ) ) then
Print( "#E <obj>.work.", comp,
" must be a matrix of (lists of) attribute lines.\n" );
result:= false;
fi;
od;
# m0, n0, m, n
for comp in [ "m0", "n0", "m", "n" ] do
if IsBound( work.( comp ) ) and
not ( IsInt( work.( comp ) ) and 0 <= work.( comp ) ) then
Print( "#E <obj>.work.", comp,
" must be a nonnegative integer.\n" );
result:= false;
fi;
od;
# heightLabelsCol, widthLabelsRow, heightRow, widthCol
for comp in [ "heightLabelsCol", "widthLabelsRow", "heightRow",
"widthCol" ] do
if IsBound( work.( comp ) ) and
not ForAll( work.( comp ), x -> IsInt( x ) and 0 <= x ) then
Print( "#E <obj>.work.", comp,
" must be a list of nonnegative integers.\n" );
result:= false;
fi;
od;
# emptyCell
if IsBound( work.emptyCell ) and
not BrowseData.IsBrowseTableCellData( work.emptyCell ) then
Print( "#E <obj>.work.emptyCell must be a ",
"table cell data object.\n" );
result:= false;
fi;
# sepCategories, startSelect
for comp in [ "sepCategories", "startSelect" ] do
if IsBound( work.( comp ) ) and
not NCurses.IsAttributeLine( work.( comp ) ) then
Print( "#E <obj>.work.", comp, " must be an attribute line.\n" );
result:= false;
fi;
od;
# startCollapsedCategory, startExpandedCategory
for comp in [ "startCollapsedCategory", "startExpandedCategory" ] do
if IsBound( work.( comp ) ) and
not ( IsList( work.( comp ) ) and
ForAll( work.( comp ), NCurses.IsAttributeLine ) ) then
Print( "#E <obj>.work.", comp,
" must be a list of attribute lines.\n" );
result:= false;
fi;
od;
# availableModes
if IsBound( work.availableModes ) and
not ( IsList( work.availableModes ) and
ForAll( work.availableModes, BrowseData.IsModeRecord ) ) then
Print( "#E <obj>.work.availableModes must be a ",
"list of mode records.\n" );
result:= false;
fi;
fi;
if IsBound( obj.dynamic ) then
dynamic:= obj.dynamic;
# indexRow, indexCol
for comp in [ "indexRow", "indexCol" ] do
if IsBound( dynamic.( comp ) ) then
l:= dynamic.( comp );
if not ( IsList( l ) and ForAll( l, IsPosInt ) ) then
Print( "#E <obj>.dynamic.", comp,
" must be a list of positive integers.\n" );
result:= false;
fi;
if not ForAll( [ 2, 4 .. Length( l ) - 1 ],
i -> IsEvenInt( l[i] ) and l[ i+1 ] = l[i] + 1 ) then
Print( "#E in <obj>.dynamic.", comp,
", the values at even positions\n",
"#E and at the subsequent odd positions must be ",
"consecutive.\n" );
result:= false;
fi;
fi;
od;
# topleft
if IsBound( dynamic.topleft ) and
not ( IsList( dynamic.topleft ) and
Length( dynamic.topleft ) = 4 and
ForAll( dynamic.topleft, IsPosInt ) ) then
Print( "#E <obj>.dynamic.topleft must be a list ",
"of four positive integers.\n" );
result:= false;
fi;
# isCollapsedRow, isCollapsedCol, isRejectedRow, isRejectedCol,
# isRejectedLabelsRow, isRejectedLabelsCol
for comp in [ "isCollapsedRow", "isCollapsedCol",
"isRejectedRow", "isRejectedCol",
"isRejectedLabelsRow", "isRejectedLabelsCol" ] do
if IsBound( dynamic.( comp ) ) then
l:= dynamic.( comp );
if not ( IsList( dynamic.( comp ) ) and
ForAll( dynamic.( comp ), IsBool ) ) then
Print( "#E <obj>.dynamic.", comp,
" must be a list of Booleans.\n" );
result:= false;
fi;
if IsBound( l[1] ) and l[1] then
Print( "#E in <obj>.dynamic.", comp,
", the first entry must be `false'\n" );
result:= false;
fi;
if not ForAll( [ 2, 4 .. Length( l ) - 1 ],
i -> l[ i+1 ] = l[i] ) then
Print( "#E in <obj>.dynamic.", comp,
", the values at even positions\n",
"#E and at the subsequent odd positions must be ",
"equal.\n" );
result:= false;
fi;
fi;
od;
# activeModes
if IsBound( dynamic.activeModes ) and
not ( IsList( dynamic.activeModes ) and
ForAll( dynamic.activeModes, BrowseData.IsModeRecord ) ) then
Print( "#E <obj>.dynamic.activeModes must be a ",
"list of mode records.\n" );
result:= false;
fi;
# selectedEntry, selectedCategory
for comp in [ "selectedEntry", "selectedCategory" ] do
if IsBound( dynamic.( comp ) ) and
not ( IsList( dynamic.( comp ) ) and
Length( dynamic.( comp ) ) = 2 and
ForAll( dynamic.( comp ),
x -> IsInt( x ) and 0 <= x ) ) then
Print( "#E <obj>.dynamic.", comp,
" must be a list of two nonnegative integers.\n" );
result:= false;
fi;
od;
if IsBound( dynamic.selectedEntry ) and
IsBound( dynamic.selectedCategory ) and
dynamic.selectedEntry <> [ 0, 0 ] and
dynamic.selectedCategory <> [ 0, 0 ] then
Print( "#E not both <obj>.dynamic.selectedEntry and ",
"<obj>.dynamic.selectedCategory can be nonzero.\n" );
fi;
# searchString
if IsBound( dynamic.searchString ) and
not IsString( dynamic.searchString ) then
Print( "#E <obj>.dynamic.searchString must be a string.\n" );
fi;
# searchParameters (check?)
# categories
if IsBound( dynamic.categories ) then
l:= dynamic.categories;
if not ( IsDenseList( l ) and Length( l ) = 3
and ForAll( l, IsList )
and Length( l[1] ) = Length( l[2] )
and ForAll( l[3], IsPosInt ) ) then
Print( "#E <obj>.dynamic.categories must be a list ",
"of three lists, the first two of the same length.\n" );
elif not IsSortedList( l[1] ) then
Print( "#E <obj>.dynamic.categories[1] must be sorted.\n" );
elif not ForAll( [ 1 .. Length( l[1] ) ],
i -> IsPosInt( l[1][i] ) and
IsRecord( l[2][i] ) and
IsBound( l[2][i].pos ) and
l[1][i] = l[2][i].pos and
IsBound( l[2][i].level ) and
IsPosInt( l[2][i].level ) and
IsBound( l[2][i].value ) and
NCurses.IsAttributeLine( l[2][i].value ) and
IsBound( l[2][i].separator ) and
NCurses.IsAttributeLine( l[2][i].separator ) and
IsBound( l[2][i].isUnderCollapsedCategory ) and
IsBool( l[2][i].isUnderCollapsedCategory ) and
IsBound( l[2][i].isRejectedCategory ) and
IsBool( l[2][i].isRejectedCategory ) ) then
Print( "#E <obj>.dynamic.categories is inconsistent.\n" );
fi;
fi;
# replay
if IsBound( dynamic.replay ) then
h:= dynamic.replay;
if not ( IsRecord( h ) and IsBound( h.logs )
and IsBound( h.pointer ) ) then
Print( "#E <obj>.dynamic.replay must be a record\n",
"#E with the components `logs' and `pointer'.\n" );
else
for log in h.logs do
if IsBound( log.steps ) and
not ( IsList( log.steps )
and ForAll( log.steps, IsPosInt ) ) then
Print( "#E <obj>.dynamic.replay.logs[...].steps must be ",
"a list of positive integers.\n" );
fi;
if IsBound( log.position ) and not IsPosInt( log.position ) then
Print( "#E <obj>.dynamic.replay.logs[...].position must be ",
"a positive integer.\n" );
fi;
if IsBound( log.replayInterval ) and
not ( IsInt( log.replayInterval )
and 0 <= log.replayInterval ) then
Print( "#E <obj>.dynamic.replay.logs[...].replayInterval ",
"must be a nonnegative integer.\n" );
fi;
if IsBound( log.quiet ) and not IsBool( log.quiet ) then
Print( "#E <obj>.dynamic.replay.logs[...].quiet must be ",
"a Boolean.\n" );
fi;
od;
fi;
fi;
if IsBound( dynamic.log ) then
if not IsList( dynamic.log ) then
Print( "#E <obj>.dynamic.log must be a list.\n" );
fi;
fi;
fi;
# The object might be a valid input for `NCurses.BrowseGeneric'.
return result;
end;
#############################################################################
##
#F BrowseData.HeightWidthWindow( <t> )
##
## It may happen that there is no component `<t>.dynamic.window';
## in this case, we use `<t>.work.windowParameters'.
##
BrowseData.HeightWidthWindow:= function( t )
if t.work.windowParameters{ [ 1, 2 ] } = [ 0, 0 ] then
return NCurses.getmaxyx( 0 );
else
return t.work.windowParameters{ [ 1, 2 ] };
fi;
end;
#############################################################################
##
#F BrowseData.SortStableIndirection( <list1>, <list2>, <funs>, <direction> )
##
## Let <list1> and <list2> be lists of the same length $n$, say,
## and <funs> be a list of comparison functions (cf. "ref:Sort").
## `BrowseData.SortStableIndirection' returns the list of length $n$
## that contains the entries of <list2> in the order that is obtained by
## sorting <list1> and <list2> in parallel (cf. "ref:SortParallel"),
## where two entries of <list1> are compared w.r.t. <funcs>,
## and additionally equal entries in <list1> are compared
## via the corresponding entries in <list2> (w.r.t. `\<').
##
## <direction> must be one of `"ascending"' or `"descending"',
## the latter means that `true' and `false' for the comparison of
## different values are interchanged.
##
BrowseData.SortStableIndirection:= function( list1, list2, funcs, direction )
list1:= List( [ 1 .. Length( list1 ) ], i -> [ list1[i], list2[i] ] );
Sort( list1, function( a, b )
local i;
for i in [ 1 .. Length( funcs ) ] do
if a[1][i] <> b[1][i] then
if funcs[i]( a[1][i], b[1][i] ) then
return direction[i] = "ascending";
else
return direction[i] <> "ascending";
fi;
fi;
od;
return a[2] < b[2];
end );
return List( list1, x -> x[2] );
end;
#############################################################################
##
#F BrowseData.IsDoneReplay( <replay> )
##
BrowseData.IsDoneReplay:= function( replay )
while replay.pointer <= Length( replay.logs ) and
replay.logs[ replay.pointer ].position
> Length( replay.logs[ replay.pointer ].steps ) do
replay.pointer:= replay.pointer + 1;
od;
return Length( replay.logs ) < replay.pointer;
end;
#############################################################################
##
#F BrowseData.IsQuietSession( <replay> )
##
BrowseData.IsQuietSession:= function( replay )
local pointer;
if not NCurses.IsStdoutATty() then
return true;
fi;
pointer:= replay.pointer;
while pointer <= Length( replay.logs ) and
replay.logs[ pointer ].position
> Length( replay.logs[ pointer ].steps ) do
pointer:= pointer + 1;
od;
return pointer <= Length( replay.logs ) and replay.logs[ pointer ].quiet;
end;
#############################################################################
##
#F BrowseData.NextReplay( <replay> )
##
BrowseData.NextReplay:= function( replay )
local currlog;
while replay.pointer <= Length( replay.logs ) and
replay.logs[ replay.pointer ].position
> Length( replay.logs[ replay.pointer ].steps ) do
replay.pointer:= replay.pointer + 1;
od;
currlog:= replay.logs[ replay.pointer ];
currlog.position:= currlog.position + 1;
return currlog.steps[ currlog.position - 1 ];
end;
#############################################################################
##
#T BrowseData.SetReplay( <data> )
#F BrowseData.SetReplay( false )
##
## <#GAPDoc Label="SetReplay_man">
## <ManSection>
## <Func Name="BrowseData.SetReplay" Arg="data"/>
##
## <Description>
## This function sets and resets the value of
## <C>BrowseData.defaults.dynamic.replay</C>.
## <P/>
## When <Ref Func="BrowseData.SetReplay"/> is called with a list <A>data</A>
## as its argument then the entries are assumed to describe user inputs for
## a browse table for which <Ref Func="NCurses.BrowseGeneric"/> will be
## called afterwards, such that replay of the inputs runs.
## (Valid input lists can be obtained from the component <C>dynamic.log</C>
## of the browse table in question.)
## <P/>
## When <Ref Func="BrowseData.SetReplay"/> is called with the only argument
## <K>false</K>,
## the component is unbound (so replay is disabled, and thus calls to
## <Ref Func="NCurses.BrowseGeneric"/> will require interactive user input).
## <P/>
## The replay feature should be used by initially setting the input list,
## then running the replay (perhaps several times),
## and finally unbinding the inputs,
## such that subsequent uses of other browse tables do not erroneously
## expect their input in <C>BrowseData.defaults.dynamic.replay</C>.
## <P/>
## Note that the value of <C>BrowseData.defaults.dynamic.replay</C> is used
## in a call to <Ref Func="NCurses.BrowseGeneric"/> only if the browse table
## in question does not have a component <C>dynamic.replay</C> before the
## call.
## </Description>
## </ManSection>
## <#/GAPDoc>
##
#T Admit more optional arguments, in order to prescribe intervals,
#T and eventually admit also records that are checked for consistency.
##
BrowseData.SetReplay:= function( arg )
local log, data;
if Length( arg ) = 1 and arg[1] = false then
# Remove the stored value.
Unbind( BrowseData.defaults.dynamic.replay );
elif Length( arg ) = 1 and IsList( arg[1] ) then
# Wrap the input list into a replay record.
data:= rec( logs:= [ rec( steps:= arg[1] ) ], pointer:= 1 );
BrowseData.defaults.dynamic.replay:= data;
# elif Length( arg ) = 1 and IsRecord( arg[1] ) then
# # Validate and set the record.
# Error( "not yet ..." );
else
Error( "BrowseData.SetReplay( <data> ) where <data> must be\n",
# "`false' or a list of input characters/numbers or a record" );
"`false' or a list of input characters/numbers" );
fi;
end;
#############################################################################
##
#F BrowseData.GetCharacter( <t> )
##
## If <C>BrowseData.IsDoneReplay</C>
## <Index Key="BrowseData.IsDoneReplay">
## <C>BrowseData.IsDoneReplay</C></Index>
## returns <K>true</K> for <A>t</A> then
## get the next user input interactively,
## otherwise read one character from <A>t</A><C>.dynamic.replay</C>.
## <P/>
## Add this character to the list <A>t</A><C>.dynamic.log</C> if the current
## list of logs is not the first one.
##
BrowseData.GetCharacter:= function( t )
local c, replay, currlog;
if t = fail then
c:= NCurses.wgetch( 0 );
if c = NCurses.keys.MOUSE then
c:= [ c, NCurses.GetMouseEvent() ];
fi;
return c;
fi;
replay:= t.dynamic.replay;
if BrowseData.IsDoneReplay( replay ) then
if not IsBound( t.dynamic.window ) then
NCurses.SetTerm();
NCurses.curs_set( 0 );
t.dynamic.window:= CallFuncList( NCurses.newwin,
t.work.windowParameters );
t.dynamic.panel:= NCurses.new_panel( t.dynamic.window );
fi;
c:= NCurses.wgetch( t.dynamic.window );
if c = NCurses.keys.MOUSE then
c:= [ c, NCurses.GetMouseEvent() ];
fi;
else
currlog:= replay.logs[ replay.pointer ];
c:= currlog.steps[ currlog.position ];
if IsChar( c ) then
c:= IntChar( c );
fi;
currlog.position:= currlog.position + 1;
if not currlog.quiet then
NCurses.napms( currlog.replayInterval );
fi;
fi;
if 1 < replay.pointer then
Add( t.dynamic.log, c );
fi;
return c;
end;
#############################################################################
##
#F BrowseData.GetPatternEditParameters( <prefix>, <default>, <paras>
#F [, <t>] )
##
## This is based on the utility <Ref Func="NCurses.GetLineFromUser"/>.
## In addition to a string, values for given parameters can be chosen.
## <P/>
## <A>prefix</A> is an attribute line shown in front of the editable region,
## <A>default</A> is the default pattern.
## <A>paras</A> must be a list of triples
## <C>[ para, values, default ]</C>,
## where <C>para</C> is a string denoting a parameter,
## <C>values</C> is the list of admissible values, and
## <C>default</C> is the position of the default value in this list.
## <P/>
## The optional argument <A>t</A> must be a browse table;
## it is used only for logging/replay.
## </P>
## If the user hits the <B>Esc</B> key then the dialog is cancelled,
## <A>paras</A> is left unchanged, and <K>fail</K> is returned.
## If the user hits the <B>Enter</B> key then <A>paras</A> is changed
## in place and the string entered by the user is returned.
##
BrowseData.GetPatternEditParameters:= function( arg )
local prefix, default, paras, t, localparas, index, win, yx, res, off,
max, empty, pos, ins, small, currpara, smallheight, bigheight,
width, winposy, winposx, loop, height, pan, ret, line, row, para,
i, c, data, pressdata, buttondown;
if Length( arg ) = 3 and NCurses.IsAttributeLine( arg[1] )
and IsString( arg[2] ) and IsList( arg[3] ) then
prefix:= arg[1];
default:= arg[2];
paras:= arg[3];
t:= fail;
elif Length( arg ) = 4 and NCurses.IsAttributeLine( arg[1] )
and IsString( arg[2] ) and IsList( arg[3] )
and IsRecord( arg[4] ) then
prefix:= arg[1];
default:= arg[2];
paras:= arg[3];
t:= arg[4];
else
Error( "usage: BrowseData.GetPatternEditParameters( <prefix>, ",
"<default>, <paras>[, <t>] )" );
fi;
if t = fail or BrowseData.IsQuietSession( t.dynamic.replay ) then
win:= 0;
else
win:= t.dynamic.window;
fi;
localparas:= List( paras, ShallowCopy );
index:= [];
for para in localparas do
if Length( para ) < 4 or para[4]( localparas ) then
Add( index, Position( localparas, para ) );
fi;
od;
yx:= NCurses.getmaxyx( win );
res:= ShallowCopy( default );
off:= NCurses.WidthAttributeLine( prefix );
max:= yx[2] - 6 - off;
empty:= RepeatedString( " ", yx[2] - 6 );
pos:= Length( res ) + 1;
ins:= true;
small:= true;
currpara:= 1;
if Length( localparas ) = 0 then
smallheight:= 3;
bigheight:= 3;
else
smallheight:= 4;
bigheight:= 5;
fi;
width:= yx[2] - 4;
winposy:= yx[1] - bigheight - Length( localparas ) - 1;
winposx:= 2;
loop:= function()
# Create the window.
height:= smallheight;
if not small then
height:= bigheight + Number( localparas,
x -> Length( x ) < 4 or x[4]( localparas ) );
winposy:= Minimum( winposy, yx[1] - height - 1 );
fi;
ret:= false;
win:= fail;
repeat
if win <> fail then
NCurses.del_panel( pan );
NCurses.delwin( win );
fi;
if t <> fail and not BrowseData.IsQuietSession( t.dynamic.replay )
and IsBound( t.dynamic.statuspanel ) then
NCurses.hide_panel( t.dynamic.statuspanel );
fi;
if t = fail or not BrowseData.IsQuietSession( t.dynamic.replay ) then
win:= NCurses.newwin( height, width, winposy, winposx );
pan:= NCurses.new_panel( win );
NCurses.savetty();
NCurses.SetTerm();
NCurses.curs_set( 1 );
NCurses.werase( win );
if small then
NCurses.wattrset( win, NCurses.attrs.BOLD );
NCurses.wborder( win, 0 );
NCurses.wattrset( win, NCurses.attrs.NORMAL );
else
NCurses.GridExt( win, rec(
trow:= 0,
brow:= height - 1,
lcol:= 0,
rcol:= yx[2] - 5,
rowinds:= [ 0, 2, height - 1 ],
colinds:= [ 0, yx[2] - 5 ] ), NCurses.attrs.BOLD );
fi;
# Show the dialog box.
NCurses.PutLine( win, 1, 1, empty );
line:= NCurses.ConcatenationAttributeLines(
[ prefix, [ NCurses.attrs.NORMAL, res ] ] );
NCurses.PutLine( win, 1, 1, line );
NCurses.wmove( win, 1, NCurses.WidthAttributeLine( line ) );
if small then
if 0 < Length( localparas ) then
NCurses.PutLine( win, 2, 1, "(down to edit parameters)" );
fi;
else
row:= 3;
index:= [];
for para in localparas do
if Length( para ) < 4 or para[4]( localparas ) then
Add( index, Position( localparas, para ) );
line:= ShallowCopy( para[1] );
for i in [ 1 .. Length( para[2] ) ] do
Append( line, " [" );
if para[3] = i then
Add( line, 'X' );
else
Add( line, ' ' );
fi;
Append( line, "] " );
Append( line, para[2][i] );
od;
Append( line, RepeatedString( " ", yx[2] - 6 - Length( line ) ) );
#T admit distributing to two lines if necessary?
if currpara + 2 = row then
line:= [ NCurses.attrs.STANDOUT, true, line ];
#T ... if yes then several rows may be marked here!
fi;
NCurses.PutLine( win, row, 1, line );
row:= row + 1;
fi;
od;
NCurses.PutLine( win, height - 2, 1,
"(up/down to choose a parameter, left/right to change the value)" );
#T admit distributing to two lines if necessary
fi;
NCurses.wmove( win, 1, off + pos );
NCurses.update_panels();
NCurses.doupdate();
fi;
# Get a character and adjust the data.
c:= BrowseData.GetCharacter( t );
if c = NCurses.keys.DOWN then
if small then
# Switch to the bigger window.
ret:= true;
break;
elif currpara < Length( index ) then
# Select the next parameter.
currpara:= currpara + 1;
fi;
elif c = NCurses.keys.UP then
if not small then
if currpara > 1 then
# Select the previous parameter.
currpara:= currpara - 1;
else
# Switch to the small window.
ret:= true;
break;
fi;
fi;
elif c = NCurses.keys.RIGHT then
if small then
if pos <= Length( res ) and pos < max then
pos:= pos + 1;
fi;
else
# Select the next value (with wrap around).
localparas[ index[ currpara ] ][3]:= ( localparas[ index[ currpara ] ][3] mod
Length( localparas[ index[ currpara ] ][2] ) ) + 1;
fi;
elif c = NCurses.keys.LEFT then
if small then
if pos > 1 then
pos:= pos - 1;
fi;
else
# Select the previous value (with wrap around).
localparas[ index[ currpara ] ][3]:= ( ( localparas[ index[ currpara ] ][3] - 2 ) mod
Length( localparas[ index[ currpara ] ][2] ) ) + 1;
fi;
elif c = NCurses.keys.IC then
ins:= not ins;
elif c = NCurses.keys.REPLACE then
ins:= not ins;
elif c in [ NCurses.keys.HOME, IntChar( '' ) ] then
pos:= 1;
elif c in [ NCurses.keys.END, IntChar( '' ) ] then
pos:= Length( res ) + 1;
if pos > max then
pos:= pos - 1;
fi;
elif NCurses.IsBackspace( c ) then
if pos > 1 then
pos:= pos - 1;
RemoveElmList( res, pos );
fi;
elif c in [ NCurses.keys.DC, IntChar( '' ) ] then
if pos <= Length( res ) then
RemoveElmList( res, pos );
fi;
elif IsList( c ) and c[1] = NCurses.keys.MOUSE then
# If the first button is pressed on this window
# then we expect that the dialog box shall be moved.
# If the first button is released somewhere
# then we move the alert box by the difference.
data:= c[2];
if 0 < Length( data ) then
if data[1].event = "BUTTON1_PRESSED" and data[1].win = win then
pressdata:= data[ Length( data ) ];
buttondown:= true;
elif data[1].event = "BUTTON1_RELEASED" and buttondown then
data:= data[ Length( data ) ];
winposy:= Minimum( Maximum( 0, winposy + data.y - pressdata.y ),
yx[1] - height );
winposx:= Minimum( Maximum( 0, winposx + data.x - pressdata.x ),
yx[2] - width );
buttondown:= false;
fi;
fi;
elif not c in [ NCurses.keys.ENTER,
IntChar(NCurses.CTRL( 'M' )), 27 ] then
if ins and Length( res ) < max then
InsertElmList( res, pos, CHAR_INT( c mod 256 ) );
pos:= pos + 1;
elif not ins and pos <= max then
res[ pos ]:= CHAR_INT( c mod 256 );
pos:= pos + 1;
fi;
fi;
until c in [ NCurses.keys.ENTER, IntChar(NCurses.CTRL( 'M' )), 27 ];
if t <> fail and not BrowseData.IsQuietSession( t.dynamic.replay )
and IsBound( t.dynamic.statuspanel ) then
NCurses.show_panel( t.dynamic.statuspanel );
fi;
if t = fail or not BrowseData.IsQuietSession( t.dynamic.replay ) then
NCurses.del_panel( pan );
NCurses.delwin( win );
NCurses.resetty();
# NCurses.endwin();
#T hier!
NCurses.update_panels();
NCurses.doupdate();
fi;
return ret;
end;
while loop() do
if 0 < Length( localparas ) then
small:= not small;
fi;
od;
# Decide whether the user cancelled or submitted.
if c = 27 then
return fail;
else
for i in [ 1 .. Length( localparas ) ] do
paras[i]:= localparas[i];
od;
return res;
fi;
end;
#############################################################################
##
#F BrowseData.CurrentMode( <t> )
##
BrowseData.CurrentMode:= t -> t.dynamic.activeModes[
Length( t.dynamic.activeModes ) ];
#############################################################################
##
#F BrowseData.PushMode( <t>, <modename> )
##
BrowseData.PushMode:= function( t, modename )
local mode;
mode:= First( t.work.availableModes, x -> x.name = modename );
if mode <> fail then
Add( t.dynamic.activeModes, mode );
t.dynamic.changed:= true;
return true;
fi;
return false;
end;
#############################################################################
##
#F BrowseData.NumberFreeCharacters( <t>, <direction> )
##
## If <direction> is `"vert"' then the return value is the number of rows in
## the window of <t>, minus the number of rows needed for the column labels,
## the header, and the footer;
## otherwise the return value is the number of columns in the window of <t>,
## minus the number of columns needed for the row labels.
##
BrowseData.NumberFreeCharacters := function( t, direction )
local s, mode, header, footer;
if direction = "vert" then
s:= BrowseData.HeightWidthWindow( t )[1]
- BrowseData.HeightLabelsColTable( t );
mode:= BrowseData.CurrentMode( t ).name;
header:= t.work.header;
if IsList( header ) then
s:= s - Length( header );
elif IsFunction( header ) then
if not IsBound( t.work.headerLength.( mode ) ) then
t.work.headerLength.( mode ):= Length( header( t ) );
fi;
s:= s - t.work.headerLength.( mode );
elif IsRecord( header ) and IsBound( header.( mode ) ) then
if not IsBound( t.work.headerLength.( mode ) ) then
t.work.headerLength.( mode ):= Length( header.( mode )( t ) );
fi;
s:= s - t.work.headerLength.( mode );
fi;
footer:= t.work.footer;
if IsList( footer ) then
s:= s - Length( footer );
elif IsFunction( footer ) then
if not IsBound( t.work.footerLength.( mode ) ) then
t.work.footerLength.( mode ):= Length( footer( t ) );
fi;
s:= s - t.work.footerLength.( mode );
elif IsRecord( footer ) and IsBound( footer.( mode ) ) then
if not IsBound( t.work.footerLength.( mode ) ) then
t.work.footerLength.( mode ):= Length( footer.( mode )( t ) );
fi;
s:= s - t.work.footerLength.( mode );
fi;
else
s:= BrowseData.HeightWidthWindow( t )[2]
- BrowseData.WidthLabelsRowTable( t );
fi;
return s;
end;
#############################################################################
##
#F BrowseData.Separator( <sep>, <i> )
##
BrowseData.Separator:= function( sep, i )
local result, j;
if NCurses.IsAttributeLine( sep ) then
result:= sep;
else
# a list of attribute lines
result:= "";
j:= i;
while 0 < j and not IsBound( sep[j] ) do
j:= j-1;
od;
if 0 < j then
result:= sep[j];
sep[i]:= result;
fi;
fi;
return result;
end;
#############################################################################
##
#F BrowseData.HeightCategories( <t>, <i>[, <l>] )
##
## is the number of rows that are currently needed for the categories of
## the <i>-th row of (the main matrix in) the browse table <t>,
## including category separator rows.
## If the optional argument <l> is given, only categories up to level <l>
## are considered.
## Note that collapsed/rejected categories do not count.
##
BrowseData.HeightCategories := function( arg )
local t, i, l, cat, cats, k, len, entry;
t:= arg[1];
i:= arg[2];
if Length( arg ) = 2 then
l:= infinity;
else
l:= arg[3];
fi;
cat:= 0;
cats:= t.dynamic.categories;
k:= PositionSorted( cats[1], i );
cats:= cats[2];
len:= Length( cats );
while k <= len do
entry:= cats[k];
if i < entry.pos
or l < entry.level
or entry.isUnderCollapsedCategory
or entry.isRejectedCategory then
break;
else
cat:= cat + 1;
# If there is a category separator and the category is expanded
# then also the separator counts.
if NCurses.WidthAttributeLine( entry.separator ) <> 0 then
if k = len then
if ForAny( [ i .. Length( t.dynamic.indexRow ) ],
x -> not BrowseData.IsHiddenCell( t, x, "vert" ) ) then
cat:= cat + 1;
fi;
elif i < cats[ k+1 ].pos then
if ForAny( [ i .. cats[ k+1 ].pos - 1 ],
x -> not BrowseData.IsHiddenCell( t, x, "vert" ) ) then
cat:= cat + 1;
fi;
elif not ( cats[ k+1 ].isUnderCollapsedCategory
or cats[ k+1 ].isRejectedCategory ) then
cat:= cat + 1;
fi;
fi;
fi;
k:= k + 1;
od;
return cat;
end;
#############################################################################
##
#F BrowseData.HeightRow( <t>, <i> )
##
## is the height of the <i>-th row of the `main' or `mainFormatted' matrix
## in the browse table <t>.
## Both row labels and entries of the main matrix are considered.
## Hidden rows have height zero.
##
BrowseData.HeightRow := function( t, i )
local result, k, mainfun, isfun, empty, row1, row2, j, entry, w;
if BrowseData.IsHiddenCell( t, i, "vert" ) then
return 0;
fi;
i:= t.dynamic.indexRow[i];
if IsBound( t.work.heightRow[i] ) then
return t.work.heightRow[i];
fi;
result:= 0;
if i mod 2 = 1 then
# This row is a separator row.
if NCurses.WidthAttributeLine( BrowseData.Separator(
t.work.sepRow, ( i + 1 ) / 2 ) ) <> 0 then
result:= 1;
#T admit arbitrary heights for separators?
fi;
else
k:= i/2;
mainfun:= t.work.Main;
isfun:= IsFunction( mainfun );
empty:= t.work.emptyCell;
row1:= [];
row2:= [];
if IsBound( t.work.mainFormatted[i] ) then
row1:= t.work.mainFormatted[i];
fi;
if IsBound( t.work.main[k] ) then
row2:= t.work.main[k];
fi;
for j in [ 1 .. t.work.n ] do
if IsBound( row1[ 2*j ] ) then
entry:= row1[ 2*j ];
elif IsBound( row2[j] ) then
entry:= row2[j];
elif isfun then
entry:= mainfun( t, k, j );
else
entry:= empty;
fi;
w:= BrowseData.HeightEntry( entry );
if result < w then
result:= w;
fi;
od;
if IsBound( t.work.labelsRow[k] ) then
for entry in t.work.labelsRow[k] do
w:= BrowseData.HeightEntry( entry );
if result < w then
result:= w;
fi;
od;
fi;
fi;
t.work.heightRow[i]:= result;
return result;
end;
#############################################################################
##
#F BrowseData.HeightRowWithCategories( <t>, <i> )
##
BrowseData.HeightRowWithCategories := function( t, i )
return BrowseData.HeightCategories( t, i )
+ BrowseData.HeightRow( t, i );
end;
#############################################################################
##
#F BrowseData.HeightLabelsCol( <t>, <i> )
##
## is the height of the <i>-th row of column labels in the browse table <t>.
## Both corner rows and entries of the column label matrix are considered.
## Hidden rows have height zero.
##
BrowseData.HeightLabelsCol := function( t, i )
local result, k, entry, w;
if t.dynamic.isRejectedLabelsCol[i] then
return 0;
elif IsBound( t.work.heightLabelsCol[i] ) then
return t.work.heightLabelsCol[i];
fi;
result:= 0;
if i mod 2 = 1 then
# This row is a separator row.
if NCurses.WidthAttributeLine( BrowseData.Separator(
t.work.sepLabelsCol, ( i + 1 ) / 2 ) ) <> 0 then
result:= 1;
#T admit arbitrary heights for separators?
fi;
else
k:= i/2;
if IsBound( t.work.labelsCol[k] ) then
for entry in t.work.labelsCol[k] do
w:= BrowseData.HeightEntry( entry );
if result < w then
result:= w;
fi;
od;
fi;
if IsBound( t.work.corner[k] ) then
for entry in t.work.corner[k] do
w:= BrowseData.HeightEntry( entry );
if result < w then
result:= w;
fi;
od;
fi;
fi;
t.work.heightLabelsCol[i]:= result;
return result;
end;
#############################################################################
##
#F BrowseData.WidthCol( <t>, <j> )
##
## is the width of the <j>-th column of the `main' or `mainFormatted'
## matrix in the browse table <t>.
## Both column labels and entries of the main matrix are considered.
## Hidden columns have width zero.
##
BrowseData.WidthCol := function( t, j )
local result, k, mainfun, isfun, empty, i, row1, row2, entry, w, row;
if BrowseData.IsHiddenCell( t, j, "horz" ) then
return 0;
fi;
j:= t.dynamic.indexCol[j];
if IsBound( t.work.widthCol[j] ) then
return t.work.widthCol[j];
fi;
if j mod 2 = 1 then
# This column is a separator column.
result:= NCurses.WidthAttributeLine( BrowseData.Separator(
t.work.sepCol, ( j + 1 ) / 2 ) );
else
result:= 0;
k:= j/2;
mainfun:= t.work.Main;
isfun:= IsFunction( mainfun );
empty:= t.work.emptyCell;
for i in [ 1 .. t.work.m ] do
row1:= [];
row2:= [];
if IsBound( t.work.mainFormatted[ 2*i ] ) then
row1:= t.work.mainFormatted[ 2*i ];
fi;
if IsBound( t.work.main[i] ) then
row2:= t.work.main[i];
fi;
if IsBound( row1[j] ) then
entry:= row1[j];
elif IsBound( row2[k] ) then
entry:= row2[k];
elif isfun then
entry:= mainfun( t, i, k );
else
entry:= empty;
fi;
w:= BrowseData.WidthEntry( entry );
if result < w then
result:= w;
fi;
od;
for row in t.work.labelsCol do
if IsBound( row[k] ) then
w:= BrowseData.WidthEntry( row[k] );
if result < w then
result:= w;
fi;
fi;
od;
fi;
t.work.widthCol[j]:= result;
return result;
end;
#############################################################################
##
#F BrowseData.WidthLabelsRow( <t>, <j> )
##
## is the width of the <j>-th column of row labels in the browse table <t>.
## Both corner columns and entries of the row label matrix are considered.
## Hidden columns have width zero.
##
BrowseData.WidthLabelsRow := function( t, j )
local result, k, row, w;
if t.dynamic.isRejectedLabelsRow[j] then
return 0;
elif IsBound( t.work.widthLabelsRow[j] ) then
return t.work.widthLabelsRow[j];
fi;
if j mod 2 = 1 then
# This column is a separator column.
result:= NCurses.WidthAttributeLine( BrowseData.Separator(
t.work.sepLabelsRow, ( j + 1 ) / 2 ) );
else
result:= 0;
k:= j/2;
for row in t.work.labelsRow do
if IsBound( row[k] ) then
w:= BrowseData.WidthEntry( row[k] );
if result < w then
result:= w;
fi;
fi;
od;
for row in t.work.corner do
if IsBound( row[k] ) then
w:= BrowseData.WidthEntry( row[k] );
if result < w then
result:= w;
fi;
fi;
od;
fi;
t.work.widthLabelsRow[j]:= result;
return result;
end;
#############################################################################
##
#F BrowseData.HeightLabelsColTable( <t> )
#F BrowseData.WidthLabelsRowTable( <t> )
##
BrowseData.HeightLabelsColTable := function( t )
local result, i;
result:= 0;
for i in [ 1 .. 2 * t.work.m0 + 1 ] do
result:= result + BrowseData.HeightLabelsCol( t, i );
od;
return result;
end;
BrowseData.WidthLabelsRowTable := function( t )
local result, j;
result:= 0;
for j in [ 1 .. 2 * t.work.n0 + 1 ] do
result:= result + BrowseData.WidthLabelsRow( t, j );
od;
return result;
end;
#############################################################################
##
#F BrowseData.LengthCell( <t>, <i>, <direction> )
##
--> --------------------
--> maximum size reached
--> --------------------
[ Dauer der Verarbeitung: 0.38 Sekunden
(vorverarbeitet)
]
|
2026-04-02
|