Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  userpref.g   Sprache: unbekannt

 
#############################################################################
##
#W  userpref.g            GAP 4 package `browse'                Thomas Breuer
##
##  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, list, 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.
      list:= data.description;
      if IsBoundGlobal( "StripMarkupFromUserPreferenceDescription" ) then
        # The strings may contain GAPDoc markup; remove this markup.
        list:= List( data.description,
                 ValueGlobal( "StripMarkupFromUserPreferenceDescription" ) );
      fi;
      for paragraph in list 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, "\
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


[ Dauer der Verarbeitung: 0.31 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge