Skip to main content

stylesheet - Forcing the front end to update style definitions


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]
];

Answer



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}

Comments

Popular posts from this blog

plotting - How to draw lines between specified dots on ListPlot?

I would like to create a plot where I have unconnected dots and some connected. So far, I have figured out how to draw the dots. My code is the following: ListPlot[{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {1, 4}, {2, 5}, {3, 6}, {4, 7}, {1, 7}, {2, 8}, {3, 9}, {4, 10}, {1, 10}, {2, 11}, {3, 12}, {4,13}, {2.5, 7}}, Ticks -> {{1, 2, 3, 4}, None}, AxesStyle -> Thin, TicksStyle -> Directive[Black, Bold, 12], Mesh -> Full] I have thought using ListLinePlot command, but I don't know how to specify to the command to draw only selected lines between the dots. Do have any suggestions/hints on how to do that? Thank you. Answer One possibility would be to use Epilog with Line : ListPlot[ {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {1, 4}, {2, 5}, {3, 6}, {4, 7}, {1, 7}, {2, 8}, {3, 9}, {4, 10}, {1, 10}, {2, 11}, {3, 12}, {4, 13}, {2.5, 7}}, Ticks -> {{1, 2, 3, 4}, None}, AxesStyle -> Thin, TicksStyle -> Directive[Black, Bold, 12], Mesh -> Full, Epilog -> { Line[ ...

equation solving - Invert and fit implicitly defined curve

I need to fit an implicitly defined curve. I thought I could get some data out of Solve , and then using FindFit . Therefore, I would like to find the relation the parametric curve defined by $F(x,y)=0$: Solve[-(1/2) + 1/2 (0.41202 BesselK[0, 0.1 Sqrt[x^2 + y^2]] + (0.101483 x BesselK[1, 0.1 Sqrt[x^2 + y^2]])/Sqrt[x^2 + y^2]) == 0, y] But I can't get an output: Solve was unable to solve the system with inexact coefficients or the system obtained by direct rationalization of inexact numbers present in the system. Since many of the methods used by Solve require exact input, providing Solve with an exact version of the system may help. >> Edit: In particular, I would like to fit the data coming from the curve with the expression of another curve, and not with a function $f(x)$. In particular, since this clearly looks like a cardioid , I would like it to fit to something like it. What other strategies could I try?

dynamic - How can I make a clickable ArrayPlot that returns input?

I would like to create a dynamic ArrayPlot so that the rectangles, when clicked, provide the input. Can I use ArrayPlot for this? Or is there something else I should have to use? Answer ArrayPlot is much more than just a simple array like Grid : it represents a ranged 2D dataset, and its visualization can be finetuned by options like DataReversed and DataRange . These features make it quite complicated to reproduce the same layout and order with Grid . Here I offer AnnotatedArrayPlot which comes in handy when your dataset is more than just a flat 2D array. The dynamic interface allows highlighting individual cells and possibly interacting with them. AnnotatedArrayPlot works the same way as ArrayPlot and accepts the same options plus Enabled , HighlightCoordinates , HighlightStyle and HighlightElementFunction . data = {{Missing["HasSomeMoreData"], GrayLevel[ 1], {RGBColor[0, 1, 1], RGBColor[0, 0, 1], GrayLevel[1]}, RGBColor[0, 1, 0]}, {GrayLevel[0], GrayLevel...