How do I add into a text cell a reference to a numbered function?
Here is one approach (code at the end):
The idea is to tag the cells via CellTags
, which allows us to easily find the cell again using Cells[CellTags->…]
. Having the CellObject
, we can obtain the number of the cell using CurrentValue[cell,{"CounterValue",counter}]
. The rest of the code is just some book-keeping.
One potential issue of this approach is that CellTags
are copied together with the cell. This means that copying a cell breaks the unique correspondence between links and cells. I try to handle this by regenerating the id tag if it occurs more than once in the current notebook. Another approach might be to set CreateCellID
to True
for the notebook, and then use that instead of tags.
Code
The code has quite a few comments which should explain everything. Please feel free to leave a comment if something is still unclear.
CreatePalette[
Button["Copy cell reference",
Module[
{cell, tags, tag, createTag},
(* get the first equation cell from the current selection *)
cell = First[
Cells[NotebookSelection[InputNotebook[]],
CellStyle -> "DisplayFormulaNumbered"],
MessageDialog["Select an equation cell!"];
Return[]
];
(* extract the CellTags and make sure they are in a list *)
tags =
Replace[CurrentValue[cell, CellTags], t : Except@_List :> {t}];
(* evaluating this will create a new unique tag for the current cell *)
createTag := With[
{newTag = CreateUUID["eqnID-"]},
SetOptions[
cell,
(* filter out all other id tags (if any) and append the new one *)
CellTags ->
Append[Select[tags, Not@*StringMatchQ["eqnID-*"]], newTag]
];
newTag
];
(* get the first id tag from the cell,
and create a new one if there is none *)
tag = SelectFirst[
tags,
StringMatchQ@"eqnID-*",
createTag
];
(* if the tag is not unique (happens if the cell was copied),
recreate it. This breaks all existing links to the cell,
but those point to another cell *)
If[Length@Cells[InputNotebook[], CellTags -> tag] > 1,
MessageDialog["Multiple cells with same tag found!
Tag of current cell will be regenerated."];
tag = createTag
];
With[(* insert the tag into the Dynamic below *)
{tag = tag},
(* copy the resulting expression to the clipboard for easy pasting *)
CopyToClipboard@Button[
Row@{
"(",
(*
dynamically get the current number of the cell with the given id tag *)
Dynamic@CurrentValue[
First[Cells[CellTags -> tag], $Failed],
{"CounterValue", "DisplayFormulaNumbered"}
],
")"
},
(* if the link is pressed,
select the cell with the matching tag *)
NotebookLocate@tag,
BaseStyle -> "Link",
Appearance -> None
]
]
]
],
Saveable -> False,
WindowTitle -> "Equation linking"
]
First, here's how the documentation expects you to create references for automatic numbering. Let's create some cells with automatic numbering, and give each cell a cell tag option:
SeedRandom[1]
CellPrint /@ Table[Cell[w, "DisplayFormulaNumbered", CellTags->w], {w, RandomWord[5]}];
Now, to create a reference to the "hurtle" formula, you would use the menu item Insert | Automatic Numbering ...
, which opens up the following dialog:
Change the Counter to "DisplayFormulaNumbered" and select "hurtle", and a reference to the "hurtle" cell will be created.
Lucas provided a palette to streamline this, by making it easier to find the cell reference (and by automatically creating a randomized cell tag if needed). Instead, it is also possible to copy the cell reference by modifying the style sheet. For instance:
Module[{event, label},
event = "MouseClicked" :> CopyToClipboard @ Cell @ TextData[{
CounterBox[
"DisplayFormulaNumbered",
Replace[CurrentValue[ParentCell @ ParentCell @ EvaluationCell[], CellTags], l_List :> First@l]
]
}];
label = Cell[
TextData[{
"(",
Cell @ BoxData @ TagBox[
CounterBox["DisplayFormulaNumbered"],
EventHandlerTag[{event}]
],
")"
}],
"DisplayFormulaEquationNumber"
];
SetOptions[
EvaluationNotebook[],
StyleDefinitions -> Notebook[
{
Cell[StyleData[StyleDefinitions->"Default.nb"]],
Cell[StyleData["DisplayFormulaNumbered"],
CellFrameLabels -> {{None, label}, {None, None}}
]
},
StyleDefinitions->"PrivateStylesheetFormatting.nb"
]
]
]
The stylesheet works by including an event handler that copies the cell reference to the clipboard when clicked. Inside of a CellFrameLabels
option, the EvaluationCell[]
refers to the Cell @ BoxData @ TagBox[_CounterBox, _EventHandler]
cell. The parent cell of that evaluation cell is the Cell[TextData[{"(", .., ")"}], "DisplayFormulaEquationNumber"]
cell, and the parent cell of that cell is the main cell, which should have the CellTags
option set. Here's a short animation of the stylesheet in action:
Now, suppose the formula to be referenced is far away, and you don't want to look for it in order to click on it? In that case, it would be convenient if there were a short cut to bring up the menu of relevant cell tags to use. Here is an input alias that does this:
CurrentValue[EvaluationNotebook[], {InputAliases, "dfn"}] = DynamicBox[
ToBoxes[
ActionMenu[
"tags",
With[{tags = CurrentValue[Cells[CellStyle->"DisplayFormulaNumbered"], CellTags]},
#:>NotebookWrite[
ParentBox@EvaluationBox[],
CounterBox["DisplayFormulaNumbered", #]
]& /@ tags
]
]
]
];
Here is an animation of the input alias in action:
Finally, if the cell tags were randomized UUID strings (as in Lucas' answer), the input alias wouldn't be very useful, which is why I recommend assigning meaningful cell tags to your formula cells, as meaningful cell tags are needed when trying to select the right cell tag from a list of cell tags.