8
$\begingroup$

Various Mathematica user interfaces are Mathematica notebook files. It seems to be quite common for these notebook files to contain only part of the information in themselves and to load different elements from one or multiple *.tr files via FrontEndResource or FrontEndResourceString. Some of the user interfaces are entirely based on a *.tr file. One example for these is the "ColorSchemeSelector", which can be displayed in the front-end using

FrontEndResource["ColorSchemeSelector"] // CellPrint

and is based on

FileNameJoin[{$InstallationDirectory, "SystemFiles", "FrontEnd", "TextResources", 
 "ColorSchemeSelector.tr"}]

How to create and use such files for my own applications?

For illustration purpose, here is an example for an user interface (without any function) that makes use of different parts of multiple front-end resource files that are part of Mathematica.

Panel @ Grid[
  {
   {FrontEndResourceString["codeAssistColorSchemeChooseMenuString"], 
    SpanFromLeft},
   {ToExpression@FrontEndResource["FEExpressions", "TimeObjectIcon"], 
    ToExpression@FrontEndResource["SliderBarHorizontal"]},
   {},
   {Button[FrontEndResource["ColorSchemeChooserStrings", "okButtonText"], 
     Null],
    Button[FrontEndResourceString["yesButtonText"], Null]}
   }]

Example

I'd want the *.tr file to be located within the folder structure of the app. However, as it is part of this question how to get that working and to test what works as expected, I copied the relatively empty TokenTranslationDictionary.tr into my $UserBaseDirectory

CopyFile[FileNameJoin[{$InstallationDirectory, "SystemFiles", "FrontEnd", "TextResources", 
    "TokenTranslationDictionary.tr"}], 
 FileNameJoin[{$UserBaseDirectory, "SystemFiles", "FrontEnd", "TextResources",
    "TokenTranslationDictionary.tr"}]]

and replaced the

{
}

with

{
"testText" -> "just some text",
"testText2" -> "some more text"
}
@|
@| just a comment
@|
@@resource myFEResource1
resource1
@|
@@resource myFEResource2
{
"TestText" -> "this is resource 2"
}
@|

which should be the correct syntax, base on the other *.tr files.

Nevertheless, after restarting Mathematica only

FrontEndResource["TokenTranslationDictionary", "testText"]

"just some text"

and

FrontEndResource["TokenTranslationDictionary", "testText2"]

"some more text"

return the expected output. But

FrontEndResourceString["myFEResource1"]

and

FrontEndResource["myFEResource2", "TestText"]

return

$Failed

Therefore it appears to me that the available resources are defined somewhere and not even adding a new resource to an existing *.tr file is possible.

How to make it possible to use my own *.tr files with their own resource definitions via FrontEndResource and FrontEndResourceString?

If that is not possible, what are the best alternatives that allow a similar information organization and importing by the front-end?

$\endgroup$
  • 2
    $\begingroup$ John Fultz said there is no workflow for users but the whole resources setup is meant to be changed as it is old and doesn't fit modern Mathematica. Yet he didn't give any estimation of when one could expect this to be done. $\endgroup$ – Kuba Nov 21 '16 at 10:20
  • 1
    $\begingroup$ I was thinking about using $FrontEndSession's TaggingRules as a replacement, what do you think? $\endgroup$ – Kuba Jan 18 '17 at 11:08
  • $\begingroup$ @Kuba Sounds like an interesting approach. One could ,e.g., separate the design code from the logic code by putting the design stuff into a file that sets the TaggingRules and then use the front-end in the logic file to get the design elements. As long as one isn't using big graphics, pre-occupying the memory isn't a problem and could remove the delays one experiences when using FrontEndResource. $\endgroup$ – Karsten 7. Jan 18 '17 at 12:47
  • 1
    $\begingroup$ I will try to use it in a project in near future so I will share my experience later. $\endgroup$ – Kuba Jan 18 '17 at 13:10
4
$\begingroup$

Here's a sample way you could do something sorta like this.

Clearly there's some bound list of resources proper that I don't yet know how to edit (despite many FrontEnd`SetValue tests and much grepping). We can take advantage of the list-of-rules format of these resources though.

I'll stick with your idea of replacing TokenTranslationDictionary.

All we'll really be doing is copying and saving files in some pre-specified directory. I thought I'd use the $TemporaryDirectory but I'm leaving the structure as if it were $UserBaseDirectory so it's a simple change if you want permanent resources:

$FETrBaseName =
  "TokenTranslationDictionary";
$FETrFileBaseName =
  "ExtendedFEResources";
If[! ListQ@$FETrResources,
  $FETrResources =
   FrontEndResource[$FETrBaseName]
  ];
$FETrBaseFile =
  FileNameJoin@{
    $InstallationDirectory,
    "SystemFiles",
    "FrontEnd",
    "TextResources",
    "TokenTranslationDictionary.tr"
    };
$FEResourceCompleteListing =
  StringCases[
      Import[#, "Text"] & /@

       FileNames["*.tr", 
        FileNameJoin@{$InstallationDirectory, 
          "SystemFiles"}, \[Infinity]],
      "@@resource " ~~ w : WordCharacter .. :> w
      ] // Flatten // DeleteDuplicates // Sort;
$FETrListing =
  AssociationMap[
   Null &,
   $FEResourceCompleteListing
   ];
$FEResourceDirectory =
  FrontEnd`FileName@{
    $TemporaryDirectory,
    "SystemFiles",
    "FrontEnd",
    "TextResources"
    };

Then we'll write our load function to configure everything:

FELoadResources[] := (
   CurrentValue[$FrontEndSession,
     {PrivatePaths, "TextResources"}
     ] =
    DeleteDuplicates@
     Prepend[
      CurrentValue[$FrontEndSession,
       {PrivatePaths, "TextResources"}
       ],
      FrontEnd`FileName[
       Evaluate@{ToFileName @@ $FEResourceDirectory}
       ]
      ];
   FrontEndExecute@FrontEnd`FlushTextResourceCaches[]
   );

Then a bit of export work:

$FETrTemplate =
  "@|
  @|
  @@resource `name`
  `value`
  @|
  ";
FETrExport[name_, value_, inputForm : True | False : True] :=

  If[value === Inherited,
   Quiet@DeleteFile@
      ToFileName @@
    Append[$FEResourceDirectory,
     If[StringQ@name,
      name <> ".tr",
      Last@name <> ".tr"
      ]
     ],
   Quiet@
    CreateDirectory[
     ToFileName @@ $FEResourceDirectory,
     CreateIntermediateDirectories -> True
     ];
   Export[
    ToFileName @@
     Append[$FEResourceDirectory,
      If[StringQ@name,
       name <> ".tr",
       Last@name <> ".tr"
       ]
      ],
    TemplateApply[
     $FETrTemplate, <|
      "name" ->
       If[StringQ@name,
        name,
        First@name
        ],
      "value" ->
       If[inputForm,
        ToString[value, InputForm],
        value
        ]
      |>],
    "Text"
    ]
   ];
FEAddResource[name_String, value_] := (
   If[KeyMemberQ[$FETrListing, name],
    FETrExport[name, value, False],
    $FETrResources =
     Normal@DeleteCases[Inherited]@
       Merge[{$FETrResources, name -> value}, Last];
    Quiet@
     CopyFile[$FETrBaseFile,
      ToFileName @@
       Append[$FEResourceDirectory,
        FileNameTake@$FETrFileBaseName]
      ];
    FETrExport[
     $FETrBaseName -> $FETrFileBaseName,
     $FETrResources
     ]
    ];
   FELoadResources[]
   );
FEAddResource[r : {(_String -> _) ..}] := (
   $FETrResources =
    Normal@DeleteCases[Inherited]@
      Merge[{$FETrResources, r}, Last];
   Quiet@
    CopyFile[$FETrBaseFile,
     ToFileName @@
      Append[$FEResourceDirectory,
       $FETrFileBaseName <> ".tr"
       ]
     ];
   FETrExport[
    $FETrBaseName -> $FETrFileBaseName,
    $FETrResources
    ]
   );

And then add a layer on FrontEndResource:

$FEExtraResources = True;
Unprotect /@ {FrontEndResource, FrontEndResourceString};
FrontEndResource[
    k_String?(! KeyMemberQ[$FETrListing, #] &)
    ] /; TrueQ@$FEExtraResources :=

  FrontEndResource[$FETrBaseName, k];
FrontEndResource /:
  Set[
    FrontEndResource[k_String],
    v_
    ] /; TrueQ@$FEExtraResources :=
  (FEAddResource[k, v]; v);
FrontEndResourceString /:
  Set[
    FrontEndResourceString[k_String?(KeyMemberQ[$FETrListing, #] &)],
    v_
    ] /; TrueQ@$FEExtraResources :=
  (FEAddResource[k, ToString@v]; 
   FrontEndResourceString[k]);
Protect /@ {FrontEndResource, FrontEndResourceString};

And when we test it:

In[134]:= FrontEndResource // DownValues // Length
FrontEndResource["asd"] = 2;
FrontEndResource // DownValues // Length
FrontEndResource["asd"]


Out[134]= 3

Out[136]= 3

Out[137]= 2

This also works for proper resources:

In[144]:= FrontEndResource // DownValues // Length
FrontEndResource["PackageHeader"] = "Package[%s]";
FrontEndResource // DownValues // Length
FrontEndResourceString["PackageHeader"]


Out[144]= 3

Out[146]= 3

Out[147]= "Package[%s]"

There's a notebook with the code here so you can download that to test it out

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.