GAP 4.8.9 installation with standard packages -- copy to your CoCalc project to get it
############################################################################# ## #W userpref.g GAP 4 package `browse' Thomas Breuer ## #Y Copyright (C) 2013, Lehrstuhl D für Mathematik, RWTH Aachen, Germany ## ## This application is preliminary, several questions are open: ## - Editing GAP code inside a dialog box is not foolproof ## because we cannot catch all syntax and runtime errors. ## - What shall happen if several user preferences are declared together, ## and at least one of them provides a values list? ## - In case of a list of choices, if the defaults are not a subset of the ## current choices then opening the selection and keeping the current ## choices yields another list than the initial value. ## (This happens for the 'PackagesToLoad' preference if not all default ## packages are installed.) ## - Should lists of choices be sorted in the selection box? ## (If yes then should the result revert this sorting, in order to achieve ## stable values if nothing was changed?) ## ############################################################################# ## #F BrowseData.TryEval( <str> ) ## ## Use a preliminary hack (suggested by Frank and Burkhard) until we can ## safely evaluate an input string which may be syntactically incorrect ## or which may cause runtime errors. ## We wrap the assignment into a function in order not to assign the value ## if the syntax is corrupted. ## BrowseData.TryEval:= function( str ) local breakOnError, errorInner, errmsg, res; # Temporarily disable the break loop. breakOnError:= BreakOnError; BreakOnError:= false; # Temporarily modify 'ErrorInner', in order to get the error message. errorInner:= ErrorInner; MakeReadWriteGlobal( "ErrorInner" ); errmsg:= false; ErrorInner:= function( arg ) errmsg:= Concatenation( arg[2] ); CallFuncList( errorInner, arg ); end; # Try to evaluate the input. Unbind( BrowseData.TryEvalResult ); str:= Concatenation( "CallFuncList( function() ", "BrowseData.TryEvalResult:=", str, "; return true; end, [] );\n" ); Read( InputTextString( str ) ); # Reset the global values. ErrorInner:= errorInner; MakeReadOnlyGlobal( "ErrorInner" ); BreakOnError:= breakOnError; if IsBound( BrowseData.TryEvalResult ) then res:= rec( value:= BrowseData.TryEvalResult, success:= true ); elif errmsg = false then res:= rec( success:= false, reason:= "Syntax error" ); else res:= rec( success:= false, reason:= errmsg ); fi; Unbind( BrowseData.TryEvalResult ); return res; end; ############################################################################# ## #F BrowseData.FormattedUserPreferenceDescription( <data>, <values>, <width> ) ## BrowseData.FormattedUserPreferenceDescription:= function( data, values, width ) local string, suff, paragraph, values_eval, default, len, i, line; string:= ""; if data = fail then # The preference (only one) is not declared. suff:= ""; Append( string, "(undeclared preference)\n" ); else # Show the description text. for paragraph in data.description do Append( string, FormatParagraph( paragraph, width, "left" ) ); od; if IsBound( data.values_strings ) then values_eval:= List( data.values_strings, BrowseData.TryEval ); if ForAny( values_eval, x -> x.success = false ) then Unbind( values_eval ); else values_eval:= List( values_eval, x -> x.value ); fi; fi; # Show the default value(s), with indent 2. if IsString( data.name ) then suff:= ""; else suff:= "s"; fi; Append( string, "\ndefault" ); Append( string, suff ); if IsFunction( data.default ) then Append( string, " (computed at runtime)" ); fi; Append( string, ":\n" ); default:= data.default; if IsFunction( default ) then default:= default(); fi; if suff = "" then default:= [ default ]; fi; len:= Length( default ); for i in [ 1 .. len ] do if IsBound( values_eval ) then line:= Position( values_eval, default[i] ); if line = fail then line:= default[i]; if IsStringRep( line ) then line:= Concatenation( "\"", line, "\"" ); else line:= String( line, 0 ); fi; else line:= data.values_strings[ line ]; fi; else line:= default[i]; if IsStringRep( line ) then line:= Concatenation( "\"", line, "\"" ); else line:= String( line, 0 ); fi; fi; if i < len then Add( line, ',' ); fi; Append( string, FormatParagraph( String( line ), width - 2, "left", [ " ", "" ] ) ); od; fi; # Show the current value(s), with indent 2. Append( string, "\ncurrent value" ); Append( string, suff ); Append( string, ":\n" ); if data <> fail and default = values then Append( string, " equal to the default" ); Append( string, suff ); Append( string, "\n" ); else len:= Length( values ); for i in [ 1 .. len ] do if IsBound( values_eval ) then line:= Position( values_eval, values[i] ); if line = fail then line:= values[i]; if IsStringRep( line ) then line:= Concatenation( "\"", line, "\"" ); else line:= String( line, 0 ); fi; else line:= data.values_strings[ line ]; fi; else line:= values[i]; if IsStringRep( line ) then line:= Concatenation( "\"", line, "\"" ); else line:= String( line, 0 ); fi; fi; if i < len then Add( line, ',' ); fi; Append( string, FormatParagraph( String( line ), width - 2, "left", [ " ", "" ] ) ); od; fi; return string; end; ############################################################################# ## #F BrowseUserPreferences( [...] ) ## ## <#GAPDoc Label="BrowseUserPreferences_section"> ## <Section Label="sec:userpref"> ## <Heading>Configuring User preferences–a Variant</Heading> ## ## A &Browse; adapted way to show and edit &GAP;'s user preferences ## is to show the overview that is printed by the &GAP; function ## <Ref Func="ShowUserPreferences" BookName="ref"/> in a &Browse; table. ## ## <ManSection> ## <Func Name="BrowseUserPreferences" Arg="package1, package2, ..."/> ## ## <Returns> ## nothing. ## </Returns> ## ## <Description> ## The arguments are the same as for ## <Ref Func="ShowUserPreferences" BookName="ref"/>, that is, ## calling the function with no argument yields an overview of all known ## user preferences, ## and if one or more strings <A>package1</A>, <M>\ldots</M> are given ## then only the user preferences for these packages are shown. ## <P/> ## <Ref Func="BrowseUserPreferences"/> opens a browse table with the ## following columns: ## <P/> ## <List> ## <Mark><Q>Package</Q></Mark> ## <Item> ## contains the names of the &GAP; packages to which the ## user preferences belong, ## </Item> ## <Mark><Q>Pref. names</Q></Mark> ## <Item> ## contains the names of the user preferences, and ## </Item> ## <Mark><Q>Description</Q></Mark> ## <Item> ## contains the <C>description</C> texts from the ## <Ref Func="DeclareUserPreference" BookName="ref"/> calls and ## the default values (if applicable), and the actual values. ## </Item> ## </List> ## <P/> ## When one <Q>clicks</Q> on one of the table rows or entries then the ## values of the user preference in question can be edited. ## If a list of admissible values is known then this means that one can ## choose from this list via <Ref Func="NCurses.Select"/>, ## otherwise one can enter the desired value as text. ## <P/> ## The values of the user preferences are not changed before one closes the ## browse table. ## When the table is left and if one has changed at least one value, ## one is asked whether the changes shall be applied. ## <P/> ## <Example><![CDATA[ ## gap> d:= [ NCurses.keys.DOWN ];; ## gap> c:= [ NCurses.keys.ENTER ];; ## gap> BrowseData.SetReplay( Concatenation( ## > "/PackagesToLoad", # enter a search string, ## > c, # start the search, ## > c, # edit the entry (a list of choices), ## > " ", d, # toggle the first four values, ## > " ", d, # ## > " ", d, # ## > " ", d, # ## > c, # submit the values, ## > "Q", # quit the table, ## > c ) ); # choose "cancel": do not apply the changes. ## gap> BrowseUserPreferences(); ## gap> BrowseData.SetReplay( false ); ## ]]></Example> ## <P/> ## The code can be found in the file <F>app/userpref.g</F> of the package. ## </Description> ## </ManSection> ## </Section> ## <#/GAPDoc> ## BindGlobal( "BrowseUserPreferences", function( arg ) local prefrec, pkglist, columnheaders, colwidths, preflist, pkgname, done, name, data, names, nam, entry, winwidth, descriptionwidth, matrix, i, descr, sel_action, table, changed, items, index; prefrec:= GAPInfo.UserPreferences; if 0 < Length( arg ) then pkglist:= List( arg, LowercaseString ); else # If no list is given then use all packages with preferences, # show "gap" first. pkglist:= Concatenation( [ "gap" ], Difference( RecNames( prefrec ), [ "gap" ] ) ); fi; ## HACKUSERPREF temporary until all packages are adjusted pkglist:= Filtered( pkglist, a -> not a in [ "Pager", "ReadObsolete" ] ); columnheaders:= [ "Package", "Pref. names", "Description" ]; colwidths:= List( columnheaders, Length ); # The entries of 'preflist' have the form # '[ pkg, names, datarecord, currvalues, newvalues ]'. preflist:= []; for pkgname in pkglist do done:= []; if IsBound( prefrec.( pkgname ) ) and IsRecord( prefrec.( pkgname ) ) then for name in Set( RecNames( prefrec.( pkgname ) ) ) do if not name in done then data:= First( GAPInfo.DeclarationsOfUserPreferences, r -> r.package = pkgname and ( name = r.name or name in r.name ) ); if data = fail or data.name = name then names:= [ name ]; else names:= data.name; fi; UniteSet( done, names ); Add( preflist, [ pkgname, names, data, List( names, x -> UserPreference( pkgname, x ) ) ] ); for nam in names do colwidths[2]:= Maximum( colwidths[2], Length( nam ) ); od; fi; od; colwidths[1]:= Maximum( colwidths[1], Length( pkgname ) ); fi; od; if IsEmpty( preflist ) then return; fi; for entry in preflist do entry[5]:= entry[4]; od; winwidth:= NCurses.getmaxyx( 0 )[2]; descriptionwidth:= winwidth - colwidths[1] - colwidths[2] - 10; # Create the rows of the table. matrix:= []; for i in [ 1 .. Length( preflist ) ] do # Adjust the case of the name if possible. pkgname:= preflist[i][1]; if IsBound( GAPInfo.PackagesInfo.( pkgname ) ) then nam:= GAPInfo.PackagesInfo.( pkgname )[1].PackageName; elif pkgname = "gap" then nam:= "GAP"; else nam:= pkgname; fi; # Compute the formatted description. descr:= BrowseData.FormattedUserPreferenceDescription( preflist[i][3], preflist[i][4], descriptionwidth ); Add( matrix, [ rec( rows:= [ nam ], align:= "tl" ), rec( rows:= ShallowCopy( preflist[i][2] ), align:= "tl" ), rec( rows:= SplitString( descr, "\n" ), align:= "tl" ), ] ); od; sel_action:= rec( helplines:= [ "edit the current entry" ], action:= function( t ) local i, names, data, currentvalues, newvalues, admissible, values, pref, index, defaults, j, results, val; if t.dynamic.selectedEntry <> [ 0, 0 ] then i:= t.dynamic.indexRow[ t.dynamic.selectedEntry[1] ] / 2; # Fetch the data of the selected user preference. names:= preflist[i][2]; data:= preflist[i][3]; currentvalues:= preflist[i][5]; newvalues:= currentvalues; if data <> fail and IsBound( data.values ) then #T this implies that names has length 1? # Bring up a list of choices. admissible:= data.values; if IsFunction( admissible ) then admissible:= admissible(); fi; if IsBound( data.values_strings ) then values:= data.values_strings; else values:= List( admissible, String ); fi; pref:= rec( items:= values, single:= not data.multi, none:= true, border:= NCurses.attrs.BOLD, align:= "c", size:= "fit", replay:= t.dynamic.replay, log:= t.dynamic.log, ); if preflist[i][5][1] <> fail then if pref.single then pref.select:= [ Position( admissible, preflist[i][5][1] ) ]; else pref.select:= List( preflist[i][5][1], x -> Position( admissible, x ) ); fi; fi; if IsBound( t.dynamic.statuspanel ) then NCurses.hide_panel( t.dynamic.statuspanel ); fi; index:= NCurses.Select( pref ); if IsBound( t.dynamic.statuspanel ) then NCurses.show_panel( t.dynamic.statuspanel ); fi; if index <> false then if pref.single then newvalues:= [ admissible[ index ] ]; else newvalues:= [ admissible{ index } ]; fi; fi; else # Let the user edit the values. defaults:= ShallowCopy( preflist[i][5] ); for j in [ 1 .. Length( defaults ) ] do if IsStringRep( defaults[j] ) then defaults[j]:= Concatenation( "\"", ReplacedString( defaults[j], "\"", "\\\"" ), "\"" ); elif defaults[j] <> fail then defaults[j]:= String( defaults[j], 0 ); fi; od; if IsBound( t.dynamic.statuspanel ) then NCurses.hide_panel( t.dynamic.statuspanel ); fi; results:= NCurses.EditFieldsDefault( "Edit user preferences", List( preflist[i][2], x -> Concatenation( x, ":" ) ), defaults, winwidth, t.dynamic.replay, t.dynamic.log ); if results <> fail then # Evaluate the strings and change the values as desired. newvalues:= []; for entry in results do val:= BrowseData.TryEval( entry ); if val.success = false then # Switching from the string to a GAP object failed. NCurses.Alert( Concatenation( val.reason, " in '", entry, "'." ), 0 ); newvalues:= fail; break; else Add( newvalues, val.value ); fi; od; if IsList( newvalues ) then if data <> fail and IsBound( data.check ) and not CallFuncList( data.check, newvalues ) then # The new values are not admissible. NCurses.Alert( Concatenation( "The values '", entry, "' are not admissible." ), 0 ); newvalues:= currentvalues; fi; else newvalues:= currentvalues; fi; fi; if IsBound( t.dynamic.statuspanel ) then NCurses.show_panel( t.dynamic.statuspanel ); fi; NCurses.update_panels(); NCurses.doupdate(); fi; if newvalues <> currentvalues then # Store the new values, they will be set later. preflist[i][5]:= newvalues; # Recompute the view value in the description column. # Note that the height of the table cell may have changed. descr:= BrowseData.FormattedUserPreferenceDescription( preflist[i][3], newvalues, descriptionwidth ); table.work.main[i][3].rows:= SplitString( descr, "\n" ); Unbind( table.work.heightRow[ 2*i ] ); # Recompute the marks in the column of the package name. for j in [ 1 .. Length( newvalues ) ] do if newvalues[j] = preflist[i][4][j] then matrix[i][2].rows[j]:= preflist[i][2][j]; else matrix[i][2].rows[j]:= [ NCurses.ColorAttr( "red", -1 ), preflist[i][2][j] ]; #T REVERSE and STANDOUT are not suitable if the table cell is selected! fi; od; fi; fi; end ); # Construct the browse table. table:= rec( work:= rec( align:= "tl", header:= t -> BrowseData.HeaderWithRowCounter( t, "GAP User Preferences", Length( matrix ) ), main:= matrix, cacheEntries:= false, labelsCol:= [ List( columnheaders, x -> rec( rows:= [ x ], align:= "l" ) ) ], sepLabelsCol:= "=", sepRow:= "-", sepCol:= [ "| ", " | ", " | ", " |" ], # Set widths for the columns. widthCol:= [ , colwidths[1],, colwidths[2],, descriptionwidth ], SpecialGrid:= BrowseData.SpecialGridLineDraw, Click:= rec( select_entry:= sel_action, select_row:= sel_action, ), ), ); # Customize the sort parameters for the two name columns. BrowseData.SetSortParameters( table, "column", 1, [ "hide on categorizing", "yes", "add counter on categorizing", "yes", "split rows on categorizing", "no" ] ); BrowseData.SetSortParameters( table, "column", 2, [ "hide on categorizing", "no", "add counter on categorizing", "no", "split rows on categorizing", "yes" ] ); # Show the table. NCurses.BrowseGeneric( table ); # If some values have been changed then ask what the user wants. changed:= Filtered( preflist, entry -> entry[4] <> entry[5] ); if not IsEmpty( changed ) then # The user may ignore the changes or save the changes. # If at least one change concerns a user preference # that belongs to the user's 'gap.ini' file then # the user may want to save the changes also in the 'gap.ini' file. items:= [ "cancel", "save values for the current GAP session" ]; if ForAny( changed, x -> ( not IsRecord( x[3] ) ) or x[3].omitFromGapIniFile = false ) then items[3]:= "save and store values in the gap.ini file"; fi; if IsBound( table.dynamic.statuspanel ) then NCurses.hide_panel( table.dynamic.statuspanel ); fi; index:= NCurses.Select( rec( items:= items, single:= true, none:= false, border:= NCurses.attrs.BOLD, align:= "c", size:= "fit", replay:= table.dynamic.replay, log:= table.dynamic.log, ) ); if IsBound( table.dynamic.statuspanel ) then NCurses.show_panel( table.dynamic.statuspanel ); fi; if index >= 2 then for entry in preflist do if entry[4] <> entry[5] then for i in [ 1 .. Length( entry[2] ) ] do SetUserPreference( entry[1], entry[2][i], entry[5][i] ); od; fi; od; fi; if index = 3 then WriteGapIniFile(); fi; fi; end ); BrowseGapDataAdd( "GAP User Preferences", BrowseUserPreferences, false, "\ the currently available user preferences, \ shown in a browse table whose columns contain the name of the package \ for which the user preference is declared, \ the name of the preference, \ and a description of its meaning, of possible values, \ of the default value, and of the current value, \ in a similar format as the the string printed by 'ShowUserPreferences'" ); ############################################################################# ## #E