2
$\begingroup$

tl;dr

I need a way to apply style edits in stylesheets, even when I can't use cmd+Shift+E

ntl;wr

A while back I wrote a suite of stylesheet editing tools to automate operations I did in batch, which basically just finds all Cell[StyleData[style],___] cells. The tools work great, except for when I need to apply the edits I've performed.

For example, try this:

new = CreateDocument[Cell[BoxData@"input", "Input"],
   System`ClosingSaveDialog -> False];
SetOptions[new,
  StyleDefinitions ->
   Notebook[{
     Cell[StyleData[StyleDefinitions -> "Default.nb"]],
     Cell[StyleData["Input"]],
     Cell[BoxData@ToBoxes@Unevaluated@
         SetOptions[Cells[][[2]],
          Background -> LightBlue
          ],
      "Input",
      InitializationCell -> True],
     Cell[BoxData@ToBoxes@Unevaluated[
         SelectionMove[
          Cells[][[2]],
          All,
          Cell
          ];
         FrontEndTokenExecute@"ToggleShowExpression"; 
         FrontEndTokenExecute@"ToggleShowExpression";
         ],
      "Input"]
     },
    StyleDefinitions -> "PrivateStylesheetFormatting.nb"]
  ];
FrontEndTokenExecute[new, "EditStyleDefinitions"];
FrontEndTokenExecute["EvaluateInitialization"]

It'll apply the styling with SetOptions but doesn't update.

I can force updating using the "ToggleShowExpression" calls in the second input cell there, but that only works if I can actually move to the cell.

Usually I'm building styles in a notebook, so I can, but sometimes this doesn't work. Case in point, I've recently been playing with the hidden style notebooks that Mathematica opens to track style changes. E.g.

$DefaultStyleNotebook :=
 
 SelectFirst[FrontEndExecute@FrontEnd`ObjectChildren[$FrontEnd], 
  Quiet@NotebookFileName@# ===
    
    FileNameJoin@{$InstallationDirectory,
      "SystemFiles", "FrontEnd", "StyleSheets", "Default.nb"} &
  ]

That's the (usually hidden) notebook that sets all the default styles. Now I can set its properties using StylesheetEdit[style, ops], but the changes can't be applied because I can't use "ToggleShowExpression".

I've looked through many of the front-end packets, but can't find where whatever "cascade-update" type functionality the front end uses to apply these updates is hidden.

Can anyone help me out? Note that the real dream would be something that I can just evaluate in the kernel, but which preserves the CellObject. (i.e. no rewriting with NotebookWrite)

Example

As an example this is the sort of syntax I'm using:

SSEdit["Input", True,
 FontColor -> Red
 ]

where the True just means create a new Cell with StyleData["Input",...] if it doesn't exist in the style definitions notebook.

After application this is what we see:

apply

And then we revert it like this:

SSEdit["Input", True,
 FontColor -> Inherited
 ]

Which we can see does indeed revert the change:

revert

But here's the annoying code I have to use to get those changes to apply:

SSApplyEdits[cells : {__CellObject}] :=     
 With[{e = EvaluationCell[]},
  Do[
   SelectionMove[c, All, Cell,
    AutoScroll -> False];
   FrontEndTokenExecute[ParentNotebook@c,"ToggleShowExpression"];
   FrontEndTokenExecute[ParentNotebook@c,"ToggleShowExpression"];,
   {c, cells}
   ];
   SelectionMove[e, After, Cell]
   ];
$\endgroup$
13
  • $\begingroup$ According to my experiments, changes applied to the in-memory "Default.nb" via yours $DefaultStyleNotebook have immediate effect on the opened Notebooks (checked with Mathematica 11.1.1 on Windows 7 x64). Is the situation different for you? From the other hand, have you tried FrontEndTokenExecute[$DefaultStyleNotebook, "ToggleShowExpression"]? $\endgroup$ Commented May 2, 2017 at 2:38
  • $\begingroup$ @AlexeyPopkov when I use SetOptions the changes don't immediately apply. Same goes when I use it on something other than the $DefaultStyleNotebook. The "ToggleShowExpression" only works when the cell is selected in the notebook. When I get a chance I'll try just using ExportPacket on the notebook. Ought to apply the changes, I think. $\endgroup$
    – b3m2a1
    Commented May 2, 2017 at 2:41
  • $\begingroup$ In my comment I meant editing the in-memory stylesheet programmatically like you showed here. What practical applications has SetOptions on the in-memory stylesheet (I just can't imagine)? $\endgroup$ Commented May 2, 2017 at 2:45
  • 1
    $\begingroup$ @AlexeyPopkov I have code that finds the StyleData cells and then I use SetOptions on those. Originally I did this with the style notebook open and so "ToggleShowExpression" sufficed but once I moved to the hidden in-memory notebooks my original code failed. Supplying the notebook object to "ToggleShowExpression" works, but opening and closing the cells can be incredibly distracting, so hopefully ExportPacket will do. $\endgroup$
    – b3m2a1
    Commented May 2, 2017 at 2:50
  • $\begingroup$ Note that instead of SetOptions you always can use NotebookRead - Insert - NotebookWrite on a CellObject. But it will open closed cell groups unfortunately. An alternative is an in-memory modification of the stylesheet Notebook expression as a whole and then writing it instead of the original using NotebookPut. $\endgroup$ Commented May 2, 2017 at 2:58

3 Answers 3

2
$\begingroup$

Updating the style definition cascade after in-memory modification of a stylesheet using SetOptions

I've found that simple (and invalid) FrontEndExecute@ExportPacket[] doesn't work for this purpose but a valid ExportPacket works:

FrontEndExecute@ExportPacket[Notebook[{Cell[""]}], "BoundingBox"]; // AbsoluteTiming
{0.0135368, Null}

(instead of "BoundingBox" one can request "InputText" or "PlainText" what appears to be slightly faster).

How I tested this. First, I find the cell defining the "Input" style:

input = Select[Cells[$DefaultStyleNotebook], 
    First@NotebookRead[#] === StyleData["Input"] &][[1]];

Then I apply SetOptions on it:

SetOptions[input, {FontSize -> 16}]

Nothing is changed visually. Now after evaluating

FrontEndExecute@ExportPacket[Notebook[{Cell[""]}], "BoundingBox"];

The font size of the input cells changes...


Update: additional methods

It is sufficient just to create an invisible Notebook and immediately close it for forcing the style definitions cascade to update:

NotebookClose[CreateNotebook[Visible -> False]] // AbsoluteTiming
{0.0696042, Null}

Toggling dynamic updating twice also works (and for a stylesheet it should be safe since it shouldn't contain Dynamic elements)

Do[FrontEndExecute[
   FrontEndToken[$DefaultStyleNotebook, "ToggleDynamicUpdating"]], {2}] // AbsoluteTiming
{0.0126106, Null}
$\endgroup$
5
  • $\begingroup$ Fascinating. I wonder why that is. Works for me though too. $\endgroup$
    – b3m2a1
    Commented May 2, 2017 at 3:35
  • $\begingroup$ @MB1965 I've updated the answer with additional methods. $\endgroup$ Commented May 2, 2017 at 14:29
  • $\begingroup$ I commented on the wrong thing... (thought I was commenting on this) I meant the first solution for the in-memory modification. I made changes it in a stylesheet notebook and the definitions didn't cascade out to the proper notebook. $\endgroup$
    – b3m2a1
    Commented Jun 8, 2017 at 4:30
  • $\begingroup$ Just tested and for whatever reason none of these are working now. It might be my 23 open notebooks (most of these are hidden FE notebooks, but maybe it tracks them?) but I'm not sure what's going on. The explicit double "ToggleShowExpression" still works, but, of course, that's an annoyingly clumsy solution. $\endgroup$
    – b3m2a1
    Commented Jun 8, 2017 at 4:34
  • $\begingroup$ @b3m2a1 Have you tried asking the tech support on this issue? Of course they can refuse supporting undocumented functionality, but you can re-formulate the question in such a way that only documented functionality will be concerned. $\endgroup$ Commented Jun 8, 2017 at 7:56
1
$\begingroup$

So I discovered the problem is just fundamentally that I'm an idiot. The issue wasn't that the notebook wasn't visible. It's that "ToggleShowExpression" was being called on the wrong notebook.

For completeness here's how I'm doing it:

StylesheetApplyEdits[cells : {__CellObject}] :=

  With[{e = EvaluationCell[]},
   Do[
    SelectionMove[c, All, Cell,
     AutoScroll -> False];
    FrontEndTokenExecute[ParentNotebook@c, "ToggleShowExpression"]; 
    FrontEndTokenExecute[ParentNotebook@c, "ToggleShowExpression"];,
    {c, cells}];
   SelectionMove[e, After, Cell]
   ];
StylesheetApplyEdits[nb : _CellObject | Automatic : Automatic] :=

  StylesheetApplyEdits[{Replace[nb, Automatic :> EvaluationCell[]]}];

This now works, so I can do silly things like:

SSEdit[$DefaultStyleNotebook, "Notebook",
 Background -> LightBlue
 ]

And all my style definitions update without me having to edit "Default.nb".

I'm still interested in the best way to do this, though. Surely there's a better way than using a front-end token.

$\endgroup$
1
$\begingroup$

Warning: this answer is about updating the default style definitions after changing CurrentValue[$FrontEndSession, DefaultStyleDefinitions] as shown here. About updating the style cascade after in-memory modification of a stylesheet using SetOptions see another answer in this thread.


One simple way to force updating of the style definitions is to invoke Export or Rasterize with a blank string:

ExportString["", "MGF"]; // AbsoluteTiming
{0.0522419, Null}
Rasterize[""]; // AbsoluteTiming
{0.0437933, Null}

More efficient approach is to call FrontEndExecute@ExportPacket[] directly:

FrontEndExecute@ExportPacket[] // AbsoluteTiming
{0.00790514, $Failed}

(it does update the style definitions despite $Failed).

It seems to be as efficient as FrontEndTokenExecute["ToggleShowExpression"] but has no drawback of potentially changing the state of selected cell(s):

FrontEndTokenExecute["ToggleShowExpression"] // AbsoluteTiming
{0.00952195, Null}
$\endgroup$
3
  • $\begingroup$ Fantastic. Does calling ExportPacket[] update all notebook styles or just the selected notebook? That is, my issue previously had been that I needed styles in a hidden stylesheet to update, but the problem was doing this in an unselected notebook. $\endgroup$
    – b3m2a1
    Commented May 1, 2017 at 16:37
  • $\begingroup$ @MB1965 I performed my tests by changing CurrentValue[$FrontEndSession, DefaultStyleDefinitions] as shown here. So actually I tested updating the default styles for newly created notebooks. $\endgroup$ Commented May 1, 2017 at 16:45
  • $\begingroup$ Okay I'll do some testing then. I don't really care about about updating default styles, just want to be able to apply edits I set in stylesheets programatically--even when the stylesheet notebook is one of the hidden ones that doesn't show up in Notebooks[]. $\endgroup$
    – b3m2a1
    Commented May 1, 2017 at 16:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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