10
$\begingroup$

What I want is pretty simple, nevertheless, after half an hour I have only unsatisfying solutions here. Let me ask you who might work more often with Mathematica's GUI elements:

Is there a way to implement a dialog in Mathematica which works in general like the autocompletion popup.

enter image description here

The dialog doesn't have to popup automatically but it should have the following features:

  • it is a fixed list of entries, where the first entry is always selected and you can use the arrow keys to navigate up and down
  • on Enter or Esc the dialog closes where in the first case, some identifier with the selected entry is returned
  • I can use a unique "short-key" which lets me directly select and return one of the entries. So for instance if my list of choices is {"Module","Block","With"} I could use e.g. m, b and w to select the corresponding entry.

I have played with various dialogs, button bars and EventHandler without luck. Bevor I investigate further, I thought asking is probably the better choice.

$\endgroup$
  • $\begingroup$ I once created a nice solution for this problem for one of my customers. I know that it was a rather lenghty function which took me hours of work. Thus, I think you are not underestimating the complexity of the problem. $\endgroup$ – Ariel Sepulveda Jul 29 '14 at 21:15
  • 2
    $\begingroup$ @ArielSepulveda I do have a hand-crafted list-dialog-clickedy-clack too for my symbol information palette but this lacks of the short-cuts and arrow keys. You always have to click. Additionally, and I haven't mentioned this here, I would like the dialog to popup at cursor (not mouse!) position. This seems impossible to achieve as far as I can tell. As always, if you want to do a real application with the dynamic UI elements it is never a one-line like advertised in the million toy-demonstrations. $\endgroup$ – halirutan Jul 29 '14 at 21:24
  • $\begingroup$ I'm not sure of what you mean by popup at cursor but maybe something can be done by means of InputField. You can move from one InputField to the next by using Tab, once you get to the desired field, typing the search string may be enough to dynamically popup the matches as a list and then use the arrow keys to navigate to the desired one, and finally, conclude the selection with a "ReturnKeyDown" which will be "collected" by the EventHandler. Thus, you may have to end up combining Dynamic with Button and EventHandler. Complicated? Yes, but maybe not impossible ;) $\endgroup$ – Ariel Sepulveda Jul 29 '14 at 21:45
  • $\begingroup$ @ArielSepulveda With popup at cursor I mean the behavior the autocompletion popup shows: It appears exactly beneath your current cursor position where you type. $\endgroup$ – halirutan Jul 29 '14 at 23:28
  • 1
    $\begingroup$ @halirutan At last WC, WRI devs (don't remember who exactly) said that those floating gui elements are going to be introduced. No specific date/time was mentioned so I'd not wait for it though. $\endgroup$ – Kuba Jan 19 '15 at 21:03
7
$\begingroup$

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.

  • Run code from the last section,

  • run testPopup["Palette"]

  • hit Ctrl+1 to interact with menu.

This one is not on cursor position because it is tough :) this one is:

  • testPopup["Cell"]

enter image description here

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

enter image description here

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 
        ]
     ]
  ];
$\endgroup$
  • $\begingroup$ I forgot to update that answer, but AttachedCell can actually take an Offset spec, so it can be in arbitrary directions. I'll get to that later today. In the meantime you can see my implementation of a top-level attacher here which will hopefully make it clearer how this works. $\endgroup$ – b3m2a1 Jan 29 '18 at 18:12
  • 1
    $\begingroup$ Thanks, I will update the answer. I was playing with Offset but after couple of FE crashes and forgetting about coordinates orientation I wrongly assumed it does not work. Thanks again. $\endgroup$ – Kuba Jan 29 '18 at 20:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.