Skip to main content
1 of 5
Kuba
  • 137.7k
  • 13
  • 289
  • 751

The following answer is closely related to Live code templates I'm playing with.

The linked topic uses AttachedCell but here we will use more documented, notebook with Frameless WindowFrame. I will also not care about preserving definitions or edge cases, if you want take a look at the source in the linked topic.

tl;dr;

Run code from the last section, run testPopup[] and hit Ctrl+1 to interact with menu.

It will be in the center of the notebook. I will explain why in a sec.

Cursor position issue

I will not implement 'show on a cursor position' popup because I think it is not worth the effort. I will tell you why and show the direction in case you are stubborn enough.

FrontEndExecute@FrontEnd`UndocumentedGetSelectionPacket@InputNotebook[]

enter image description here

with its "VirtualSelectionRectangle" it can be used to aquire a little info about cursor position. As I will show now, it is not enough with respect to other tools we need to use.

We can either use AttachedCell (undocumented) or a WindowFrame -> Frameless palette for our popup.

  • According to what we know about an AttachedCell 133523. Its position can not be specified by absolute or scaled coordinates. And the offset it supports is only in {-1,1} direction. Not precise enough to use it.

  • WindowFrame->"Frameless" palette could be used and if we want to align it with the cursor we need to know parent notebook's WindowMargins (easy). But coordinates returned from UndocumentedGetSelectionPacket do not take into account DockedCells, additionaly Rasterize[#, "BoundingBox"]& will not help with this as it misses specific margins/frames.

At this point I decided I don't care. And we didn't even discussed that you need to do many of those calculations Kernel side. And you need to be flexible in case the selection is close to the bottom.right edge, then the layout should rise instead of drop.

Key events

Whatever wrapper you use the key point is to create a DynamicModule that will temporarily overwrite parent notebook's NotebookEventActions on Initialization and will revert old settings on Deinitialization (you thought you will never use this opition, didn't you?).

Once Arrows/Enter/Esc are overwritten there is no problem in "KeyDown" event and check if an event key isn't a member of defined shortcuts.

Demo code

CenterToParent is taken from How to center a dialog with respect to a parent notebook

testPopup[]:=CreateDocument[{},
 StyleDefinitions -> Notebook[{
  Cell[StyleData[StyleDefinitions -> "Default.nb"]],
  Cell[StyleData["Notebook"],
    NotebookEventActions -> {{"MenuCommand", "InsertNewGraphic"}:>openPopup[]} (*Ctrl+1*)
  ]    
 }]
 ]

items = { 
<|"lbl"->"item 1","action":>NotebookWrite[InputNotebook[], "item1"],"shortkey" -> "m"|>, 
<|"lbl"->"item 2","action":>NotebookWrite[InputNotebook[], "item2"],"shortkey" -> "k"|>,
<|"lbl"->"item 2","action":>NotebookWrite[InputNotebook[], "item3"],"shortkey" -> "l"|>
};


openPopup[]:=With[
  { pos = Lookup[
      FrontEndExecute@FrontEnd`UndocumentedGetSelectionPacket@InputNotebook[]
    , "VirtualSelectionRectangle"
    , False  
  ]
  }
, NotebookPut @ CenterToParent @ Notebook[
    List @ Cell[BoxData @ ToBoxes @ popupMenu[]
    , CellFrameMargins->{{0, 0}, {0, 0}}
    , CellMargins->{{0, 0}, {0, 0}}    
    ]
    
, StyleDefinitions->"Dialog.nb"
, WindowClickSelect->False
, WindowFloating->True
, WindowFrame->"Frameless"
, WindowSize->All
, Background->LightBlue
, WindowElements->{}
, WindowFrameElements->{}
,WindowMargins-> {{Automatic,Automatic},{Automatic,Automatic}}
]];

popupMenu[]:=DynamicModule[
      {item=1,thisObject, parentObject, menuClose, items = items}
    , Table[With[{i=i},
       Button[
         Style[
         StringTemplate["`lbl` (`shortkey`)"]@items[[i]], Bold, FontColor-> Dynamic@If[item==i,Darker@Red,Darker@Green]]
       , items[[i,"action"]]; menuClose[]
      , FrameMargins->15
       , ImageMargins->0
       , Appearance->FrontEndResource["FEExpressions","GrayNinePatchAppearance"]
       ]]
      , {i, Length @ items}
     ]//Column[#,Spacings->0]&
   , Initialization:>(
       thisObject = EvaluationNotebook[];
       parentObject = InputNotebook[];
       menuClose[]:=NotebookClose @ thisObject;
       
       CurrentValue[parentObject, NotebookEventActions] = {
         "UpArrowKeyDown":>(item=Mod[item-1,2,1])
       , "DownArrowKeyDown":>(item=Mod[item+1,2,1])
       , "EscapeKeyDown":>menuClose[]
       , "ReturnKeyDown" :> (items[[item, 2]]; menuClose[])
       , "KeyDown" :> If[
           MemberQ[items[[;;, "shortkey"]], CurrentValue["EventKey"]]
         , (#action& /@ Select[items, #shortkey === CurrentValue["EventKey"]&]);menuClose[]
         ]
       , "MouseClicked" :>  menuClose[] 
      }
    )
   , Deinitialization:>(
      CurrentValue[parentObject,NotebookEventActions]=Inherited
    )
  ];


CenterToParent::usage =  "CenterToParent[DialogInput[...]] etc, will make the dialog centered with respect to the parent notebook";

CenterToParent // Attributes = {HoldFirst};

CenterToParent[ dialog_[whatever__, opts : OptionsPattern[]]  ] := With[
    {apc = AbsoluteCurrentValue}
  , With[
        { parentCenter = Transpose @ {
              apc[WindowMargins][[;; , 1]] + .5 apc[ WindowSize]
            , {Automatic, Automatic}
          }
        }
      , dialog[
            whatever
          , NotebookDynamicExpression :> Refresh[
                SetOptions[
                    EvaluationNotebook[]
                  , WindowMargins -> (parentCenter - Transpose[{.5 apc[WindowSize], {0, 0}}])
                  , NotebookDynamicExpression -> None
                ]
              , None
            ]
          , opts 
        ]
     ]
  ];
Kuba
  • 137.7k
  • 13
  • 289
  • 751