4
$\begingroup$

Note: The code below requires version 12.0.

Problem Overview: I am having some strange behavior with an ActionMenu, where the items in the menu don't show up until the mouse passes over them, as shown below. I would welcome any insights into how to fix this problem.

enter image description here

Background: This is part of a package I am developing to allow sophisticated label/symbol notation for both input cells and output cells. My specific application is Chemistry, but the approach should be more generally applicable. The main notion is to have a list of string notations and associate them with internal symbols, and have those string notations inside a TemplateBox with an assigned tag that helps with style. I want the user to be able to define these notations on-the-fly, because this is a general tool I use in teaching. Using this approach for output cells is easy, but is a bit more challenging for input cells, because I need a way to enter the string notation already associated with the TemplateBox tag. I have a way to do it using palettes, but am exploring a way to do it using an escape-alias and an ActionMenuBox. That is where I am running into this strange problem.

Setup Code: The following loads the Notation package, specifies the stylesheet and adds a custom style that helps with formatting, and defines a function that takes a list of label/symbol pairs and generates the notation associations.

<< Notation`
SetOptions[EvaluationNotebook[], 
  StyleDefinitions -> 
    Notebook[
      {Cell[
         StyleData[StyleDefinitions -> 
         FrontEnd`FileName[{"Report"}, "StandardReport.nb", 
         CharacterEncoding -> "UTF-8"]]], 
       Cell[StyleData["chemical"], StyleMenuListing -> None, 
         TagBoxOptions -> {SyntaxForm -> "symbol"}, 
         ShowStringCharacters -> False, ShowAutoStyles -> False, 
         ZeroWidthTimes -> True, 
         TemplateBoxOptions -> 
         {DisplayFunction -> (RowBox[{#}] &), 
         SyntaxForm -> "symbol"}]}]]
AssignLabels[list_] := 
  Block[{}, 
    Do[Notation[DoubleLongLeftRightArrow[
      ParsedBoxWrapper@TemplateBox[{"\""<>list[[i,1]]<>"\""}, 
      "chemical", Editable -> False, Selectable -> False], 
      ParsedBoxWrapper@SymbolName[list[[i, 2]]]]], 
     {i, Length[list]}];]

The Palette Version that Works: To give you an idea of what I am trying to accomplish, here is how I make a palette to insert the tagged labels:

ChemPalette[name_, list_, width_] := 
  CreatePalette[Multicolumn[PasteButton @@@ list, width], 
  WindowTitle -> name]

Example Usage: The following code assigns eight example labels and generates a palette for their entry.

assignments = {{"\!\(\*SubscriptBox[\(C\), \\(6\)]\)\!\ 
  (\*SubscriptBox[\(H\), \(6\)]\)O", C6H6O}, {"(\!\ 
  (\*SubscriptBox[\(NH\), \(4\)]\)\!\(\*SubscriptBox[\
  \()\), \(2\)]\)\!\(\*SubscriptBox[\(SO\), \(4\)]\)", 
  AmmoniumSulfate}, {"\!\(\*SubsuperscriptBox[\(SO\), \(4\), \
  \(\(2\)\(-\)\)]\)", Sulfate}, {"\!\(\*SubscriptBox[\(H\), \ 
  (2\)]\)O (g)", WaterVapor}, {"\!\(\*SubscriptBox[\(\ 
  [CapitalDelta]\), \(f\)]\)\!\\(\*SubsuperscriptBox[\(H\), 
  \(\*SubscriptBox[\(H\), \(2\)] O \((g)\)\\), \(o\)]\)", 
  \[CapitalDelta]fHWaterVapor}, \{"\!\(\*SubsuperscriptBox[
  \(k\), \(sq\), SubscriptBox[\(C\), \(60\)]]\\)", ksqC60}, 
  {"\!\(\*SubsuperscriptBox[\(\[Null]\), \(6\), \\(12\)]\)C",
  Carbon12}, {"[\!\(\*SuperscriptBox[\(H\), 
  \(+\)]\)]",HplusConc}};

In Mathematica, that code looks like this:

enter image description here

And then:

AssignLabels[assignments];
ChemPalette["Example", assignments, 3];

This generates a palette that looks like this:

enter image description here

And I can then use this palette to enter any of my labels and have my input cells (and the resulting output cells) look like perfectly chemistry-standard notation.

The ActionMenu Version: Because I tend to prefer keyboard entry to palette entry, I have been exploring ways to do the same thing in a more keyboard-centric manner. Based on a suggestion by Carl Woll on this thread, I tried the following:

EscMenu[shortcut_, title_, list_] := 
  AddInputAlias[
    shortcut -> 
      ParsedBoxWrapper[ActionMenuBox[title, 
        (ToBoxes[#2] :> NotebookWrite[EvaluationBox[], 
        ToBoxes@#2]) & @@@ list, 
        Appearance -> "PopupMenu", AutoAction -> True]]]
EscMenu["chem", "Example", assignments]

This does exactly what I want (well, mostly... bonus question to refine it further below), except that all of the menu entries appear blank until the mouse passes over them (as shown at the top of this question). Any ideas?

Bonus Question: This approach still requires the mouse. What would be really nice is if the Esc-chem-Esc sequences could then be followed by arrow key selection of the options rather than having to use the mouse. Any ideas on how to implement that?

Context: The application is a set of code to be distributed to students in a chemistry class. They will execute the code, and then on-the-fly generate the label/symbol associations they need for the particular problem they are trying to solve from a small set of simple function calls. They should be able to generate one palette or ActionMenu for one problem (on, say, an exam or homework), and then generate a different one with completely different labels/symbols for a second problem without having to restart the kernel. The code they take from my materials they will copy-and-paste in to use; that code can be sophisticated and even obscure, because all they will do is copy-and-paste. Their usage of it, however, needs to be simple. They understand a bit of Mathematica notation, but not a huge amount, because Mathematica is emphatically not the focus of the class.

Edited to Add: A suggestion was made to write boxes directly. I will explain here why I don't think that works in this case. Let us look at a particular example notation I am using: enter image description here. I can enter that in a string by typing the following key sequence: "EscDEscCtrl-fCtrlSpaceHCtrl-HCtrl-2CtrlSpaceO(g)Ctrl%oCtrlSpace". My students already (for other purposes) know all of the Ctrl sequences in that, and it gets formatted correctly. They have the skills to generate such a string, but do not have the skills to generate box representations, especially with AdjustmentBoxes. If I do not enclose that in a string, the formatting gets screwed up: enter image description here. I found a way using the Notation package to assign aliases (detailed above) that lets me take that string and make it (with an embedded TemplateBox tax) the box representation displayed. This is the code that accomplishes this:

ParsedBoxWrapper@TemplateBox[{"\""<>list[[i,1]]<>"\""}, 
  "chemical", Editable -> False, Selectable -> False]

And unfortunately, that approach doesn't seem to work when placed into the ActionMenuBox context.

However, in trying a bunch of things to provide this clarification, I may have hit on a solution. I will update shortly.

$\endgroup$
0

2 Answers 2

3
+50
$\begingroup$

I'll provide two approaches

Cleaner

My guess is this is an issue with the nasty syntax for using boxes in strings. A better approach is probably to write the boxes directly. Here's one that works for me:

CurrentValue[EvaluationNotebook[], {InputAliases, "ch"}] =
  ActionMenuBox[
   DynamicBox[
    FEPrivate`Part[
     FEPrivate`ObjectContents[
      FrontEnd`ParentBox[FrontEnd`EvaluationBox[]]],
     2, 1, 1
     ]
    ],
   MapThread[
    # :> NotebookWrite[EvaluationBox[], #2] &,
    Transpose@{
      {SubscriptBox[ToBoxes@"C", ToBoxes@"6"], C6},
      {
       RowBox[{
         SubscriptBox[ToBoxes@"H", "2"],
         AdjustmentBox[ToBoxes@"O", BoxMargins -> {{-.5, 0}, {0, 0}}]
         }],
       WaterVapor
       }
      }
    ],
   Appearance -> "PopupMenu",
   AutoAction -> True,
   BaseStyle -> {
     ShowStringCharacters -> False,
     FontWeight -> Plain,
     FontFamily -> "Times"
     }
   ];

enter image description here

I also added some fun tweaks, like making it automatically populate itself with the first label passed and changing up the BaseStyle so you can make it look different.

No mouse

This one doesn't require the mouse at all, but you'll need to do more work to make it nice:

moveTo[tag_, i_, ntags_] :=
 FrontEnd`BoxReferenceFind[
   FE`BoxReference[
    #,
    {tag <> "_" <> ToString[Mod[i, ntags, 1]]},
    FE`SearchStart -> "StartFromBeginning"
    ]
   ] &
chooseMeBox[(label_ :> value_), func_, tag_, i_, ntags_] :=
  TagBox[
   PanelBox[
    ToBoxes@Pane[label, ImageSize -> {100, Automatic}],
    Appearance -> {
      "Default" -> 
       FrontEnd`FileName[{"Popups", "CodeCompletion"}, 
        "CARectBG.9.png"], 
      "Hover" -> 
       FrontEnd`FileName[{"Popups", "CodeCompletion"}, 
        "row_hover.9.png"],
      "Pressed" -> 
       FrontEnd`FileName[{"Popups", "CodeCompletion"}, 
        "row_hover.9.png"],
      FEPrivate`If[
       FEPrivate`SameQ[
        FrontEnd`CurrentValue[FrontEnd`EvaluationBox[], 
         "SelectionOver"],
        True
        ],
       "Pressed",
       Automatic
       ]
      },
    BoxID -> tag <> "_" <> ToString[i]
    ],
   With[{d = moveTo[tag, i + 1, ntags], u = moveTo[tag, i - 1, ntags]},
    EventHandlerTag[
     {
      "ReturnKeyDown" :> func[value],
      "DownArrowKeyDown" :> FrontEndExecute[d[EvaluationNotebook[]]],
      "UpArrowKeyDown" :> FrontEndExecute[d[EvaluationNotebook[]]],
      Method -> "Preemptive",
      PassEventsDown -> False,
      PassEventsUp -> False
      }
     ]
    ]
   ];
chooserBox[label_, rules_, tag_] :=
 With[{ntags = Length@rules},
  PanelBox[
   GridBox[
    Prepend[
      {
       TagBox[
        PanelBox[
         ToBoxes@Pane[label, ImageSize -> {100, Automatic}],
         Appearance -> {
           "Default" ->
            
            FrontEnd`FileName[{"Popups", "CodeCompletion"}, 
             "CARectBG.9.png"]
           }
         ],
        With[{d = moveTo[tag, 1, ntags], 
          u = moveTo[tag, ntags, ntags]},
         EventHandlerTag[
          {
           
           "DownArrowKeyDown" :> 
            FrontEndExecute@d[EvaluationNotebook[]],
           "UpArrowKeyDown" :> FrontEndExecute@u[EvaluationNotebook[]],
           Method -> "Preemptive",
           PassEventsDown -> False,
           PassEventsUp -> False
           }
          ]
         ]
        ]
       }
      ]@
     MapIndexed[
      {
        chooseMeBox[
         #, 
         NotebookWrite[
           Nest[ParentBox, EvaluationBox[], 3], #] &, 
         tag, #2[[1]], ntags
         ]
        } &,
      rules
      ],
    GridBoxSpacings -> {"Rows" -> {{0}}}
    ],
   Appearance -> {"Default" -> 
      FrontEnd`FileName[{"Popups", "CodeCompletion"}, 
       "top_left.9.png"]},
   BoxID -> tag
   ]
  ]

Then:

CurrentValue[EvaluationNotebook[], {InputAliases, "ch"}] =
  With[
   {
    baseBox =
     chooserBox[
      "ChemTemplates",
      {
       RawBoxes@SubscriptBox[ToBoxes@"C", ToBoxes@"6"] :> C6, 
       RawBoxes@
         RowBox[{SubscriptBox[ToBoxes@"H", "2"], 
           AdjustmentBox[ToBoxes@"O", 
            BoxMargins -> {{-.5, 0}, {0, 0}}]}] :> WaterVapor
       },
      "ChemTemplates"
      ]
    },
   DynamicBox[
    baseBox
    ]
   ];

enter image description here

$\endgroup$
4
  • $\begingroup$ I like the FontWeight and FontFamily tweaks; thank you for those! They are easy to add to my "chemistry" style definition. Autopopulation is also nice; thanks for the example code for it! Unfortunately, writing the boxes directly isn't really in the cards for this application. I barely understand box structures myself, and there is no way I want my users (chemistry students) to have to manually write boxes. They format within a string. I need a way to do the conversion from a string, or directly use the display form from the Notation assignment. I will edit the OP to explain a bit better. $\endgroup$ Commented Apr 21, 2019 at 22:21
  • $\begingroup$ In researching my update above, I hit on the following approach: replace the # before the :> in your code with ToBoxes[Style[#1,"chemical"]]. This seems to solve the display problem while still maintaining my desired style. Thank you for the inspiration that led me to this solution! I will look at your portion concerning the no-mouse option next. $\endgroup$ Commented Apr 21, 2019 at 22:55
  • $\begingroup$ It seems I can get a variant of your mouseless version working using my approach with: EscMenu[shortcut_, list_, tag_] := AddInputAlias[shortcut->ParsedBoxWrapper[DynamicBox[chooserBox[tag,MapThread[#2 :>ToBoxes[#2] &,list[Transpose]],tag]]]] Call: EscMenu["test",assignments,"Some Examples"] I took out ImageSize specifications hard coded to remove some wrapping problems. I am having two remaining problems with it. First, it takes about ten seconds for Mathematica to respond after I get the 't' typed in. Predictive menu issue? Also, both the up and down key seem to move down. $\endgroup$ Commented Apr 22, 2019 at 0:19
  • $\begingroup$ I think my remaining problems are far enough removed from the original posting that it is probably worth calling this a successfully answered question. I will add my own answer below to give the complete version that works for me, but will mark your answer as accepted. Thank you! $\endgroup$ Commented Apr 22, 2019 at 0:54
0
$\begingroup$

So as to not duplicate large swaths of code, here is what finally seems to be working for me. In my "Setup Code" I added the following options to the StyleData["chemical"] portion:

FontWeight -> Plain, FontFamily -> "Times"

I was able to get the first ideas by @b3m2a1 to work (a mouse-selectable ActionMenu) to work using this code:

EscMenu[shortcut_, title_, list_] := 
  AddInputAlias[
   shortcut -> 
    ParsedBoxWrapper[
     ActionMenuBox[title, 
      MapThread[
       ToBoxes[Style[#1, "chemical"]] :> 
         NotebookWrite[EvaluationBox[], ToBoxes[#2]] &, 
       list\[Transpose]], Appearance -> "PopupMenu", 
      AutoAction -> True]]]

Which I then call like so:

EscMenu["test", "testing", assignments]

I can now do this:

enter image description here

Which is absolutely magnificent!

For the no-mouse version from @b3m2a1, I got the ideas to work by modifying his main swath of code by (a) removing the ImageSize statements to fix a wrapping problem, and fixing a d[... that should have been a u[... I then called it using the following:

EscMenu2[shortcut_, list_, tag_] := 
  AddInputAlias[
   shortcut -> 
    ParsedBoxWrapper[
     DynamicBox[
      chooserBox[tag, MapThread[#2 :> ToBoxes[#2] &, list\[Transpose]],
        tag]]]]

And then call it using:

EscMenu2["test2", assignments, "Some Examples"]

When I turn Code Assist off, I am able to do the following:

enter image description here

With Code Assist turned on, however, it takes a full 10 seconds for the Code Assist box to pop up, hanging the window until it completes. I am looking for a way to turn Code Assist off programatically for just these aliases, but have not yet found one. I also would love to have a solution that works with both keyboard and mouse. But both of those aspects are probably beyond the scope of the original question (and bounty), so I am going to mark @b3m2a1 's answer as accepted.

Edited to Add: I now have a way to accept entry by either keyboard or mouse! Simply add

"MouseClicked" :> func[value]

to the EventHandlerTag in the chooseMeBox definition.

$\endgroup$

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.