Edit
Once again b3m2a1 provided more detailed information about a FE's feature so I have to retract my claim that it is not worth an effort to create a popup on cursor position as opposed to e.g. in the corner of the notebook.
Main
The following answer is closely related to Live code templates I'm working on in a free time.
I will outline two alternative methods, attached cell vs frameless palette. (the linked topic uses the former one but without attaching on a cursor position)
tl;dr;
Make sure you saved your work, this is a rough implementation and the "Cell" approach uses an undocumented AttachedCell
.
This one is not on cursor position because it is tough :) this one is:
Cell vs. Palette
Palette is not clipped by a parent notebook what is very convenient when the cursor is close to the bottom/right edge or the menu is not small.
Palette notebook is documented
AttachedCell
can be placed directly on a cursor position, thanks to an UndocumentedGetSelectionPacket
. It is not easy with Palette because Undocu...
does not account on DockedCells
and additionaly we need to do WindowMargins
arithmetic.
Cursor position
FrontEndExecute @ FrontEnd`UndocumentedGetSelectionPacket @ InputNotebook[]
its "VirtualSelectionRectangle"
value is a cursor position with respect to the bottom left edge of the very bottom docked cell (or a nb menu).
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[type:"Cell"|"Palette":"Cell"]:=CreateDocument[{}
, WindowTitle -> type <> " popup menu"
, StyleDefinitions -> Notebook[{
Cell[StyleData[StyleDefinitions -> "Default.nb"]]
, Cell[StyleData["Notebook"]
, NotebookEventActions -> {{"MenuCommand", "InsertNewGraphic"}:>openPopup[type]} (*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["Palette"]:= NotebookPut @ CenterToParent @ Notebook[
List @ Cell[BoxData @ ToBoxes @ popupMenu["Palette"]
, 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->{}
];
openPopup["Cell"] := Module[{sel,fromTop, fromLeft}
, Catch[
sel = FrontEndExecute@FrontEnd`UndocumentedGetSelectionPacket@
InputNotebook[]
; If[
MatchQ[sel, KeyValuePattern[{"CellSelectionType" -> "ContentSelection", "SelectionType" -> "CursorRight"}]]
, {fromLeft,fromTop} = Lookup[sel, "VirtualSelectionRectangle", {{0,0},{0,0}}][[All, 1]]
, Beep[];Throw@$Canceled
]
; MathLink`CallFrontEnd[
FrontEnd`AttachCell[
EvaluationNotebook[]
, Cell @ BoxData @ ToBoxes @ popupMenu["Cell"]
, {Offset[{-fromLeft,-fromTop},0], {Left,Top}}
, {Left,Top}
, "ClosingActions"->{ "OutsideMouseClick" }
]
]
]
];
popupMenu[type_]:=DynamicModule[
{item=1,thisObject, parentObject, menuClose, items = items, itemsLength = Length@items}
, Table[With[{i=i},
EventHandler[#, "MouseEntered":>(item = 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, itemsLength}
]//Column[#,Spacings->0]&
, Initialization:>(
If[
type === "Palette"
,
thisObject = EvaluationNotebook[];
menuClose[]:=NotebookClose @ thisObject
,
thisObject = EvaluationCell[];
menuClose[]:=NotebookDelete @ thisObject
];
parentObject = InputNotebook[];
CurrentValue[parentObject, NotebookEventActions] = {
"UpArrowKeyDown":>(item=Mod[item-1,itemsLength,1])
, "DownArrowKeyDown":>(item=Mod[item+1,itemsLength,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
]
]
];