5
$\begingroup$

I want to interactively manipulate the contents of a GridBox using keyboard commands. To approach this I need to control selection with something like SelectionMove but I cannot figure out the right parameters. How can I programmatically select individual elements and groups of elements within a GridBox?

$\endgroup$
  • $\begingroup$ Are you willing to wrap the elements at all? I think I can make a nice FE-friendly solution if I can wrap them in things to contain a BoxID. $\endgroup$ – b3m2a1 Feb 23 at 6:17
  • $\begingroup$ @b3m2a1 Pardon me for not replying, I had food poisoning. $\endgroup$ – Mr.Wizard Feb 25 at 10:16
  • $\begingroup$ oh no worries about that. I hope you are feeling better now. $\endgroup$ – b3m2a1 Feb 25 at 10:18
  • 1
    $\begingroup$ @b3m2a1 Half way there; thanks. $\endgroup$ – Mr.Wizard Feb 25 at 10:22
4
$\begingroup$

Finally got this working. It's a huge pain to find the current element in Mathematica, as we all know so that was basically what I needed to figure out how to find. But I did! And here's how I'm finding that:

Get["https://raw.githubusercontent.com/b3m2a1/mathematica-tools/master/FENaming.wl"]
getCurrentPosition[tag_] :=
  Module[
   {
    nextObj =
     Replace[
      BoxObject[
       FENamed[_, tag <> "_gridElement"],
       {FE`BoxOffset -> FE`BoxParent[3]}
       ],
      Except[_BoxObject] :>
       EvaluationBox[]
      ],
    prevObj,
    body,
    inds
    },
   prevObj =
    Replace[
     FrontEndExecute@
      FrontEnd`ObjectChildren@
       FrontEndExecute@FrontEnd`ParentBox[nextObj],
     {
      {___, left_, nextObj, ___} :> left,
      {nextObj, ___, last_} :> last,
      _ -> None
      }
     ];
   If[prevObj === None,
    body = NotebookRead[prevObj];
    inds =
     FirstCase[nextObj,
      TagBox[__, 
        BoxID -> (s_String?(StringStartsQ[tag <> "_Position"]))] :>
       ToExpression@
        StringSplit[StringSplit[s, "_Position", 2][[-1]], ","],
      {-1, -1},
      6
      ];
    inds = inds - {0, 1},
    body = NotebookRead[prevObj];
    inds =
     FirstCase[body,
      TagBox[__, 
        BoxID -> (s_String?(StringStartsQ[tag <> "_Position"]))] :>
       ToExpression@
        StringSplit[StringSplit[s, "_Position", 2][[-1]], ","],
      {-1, -1},
      6
      ]
    ];
   inds
   ];

Then a very simple mover within a tagged grid and updated element wrapper:

moveAcrossTags[tag_, {dx_, dy_}] :=

 With[{pos1 = getCurrentPosition[tag]},
  NotebookLocate@
   FENamed[_, 
    tag <> "_Position" <>
     StringRiffle[
      Map[ToString, pos1 + {dx, dy}],
      ","
      ]
    ]
  ]

makeNamedGridElements[gridEls_, tag_String] :=
 MapIndexed[
  EventHandler[
    FENamed[
     FENamed[#, 
      tag <> "_Position" <> StringRiffle[Map[ToString, #2], ","]], 
     tag <> "_gridElement"],
    {
     "RightArrowKeyDown" :> moveAcrossTags["realGrid", {0, 1}],
     "LeftArrowKeyDown" :> moveAcrossTags["realGrid", {0, -1}],
     "DownArrowKeyDown" :> moveAcrossTags["realGrid", {1, 0}],
     "UpArrowKeyDown" :> moveAcrossTags["realGrid", {-1, 0}]
     }
    ] &,
  gridEls,
  {2}
  ]

Finally:

realGrid =
 Grid[
  makeNamedGridElements[#, "realGrid"] &@
   Partition[
    Thread@
     Graphics[
      Thread[{RandomColor[25], Disk[]}],
      ImageSize -> 25
      ], 
    5]
  ]

enter image description here

Original

Here's the start of a solution using the FENamed structure I defined here. We just attach a findable tag to each thing via the BoxID parameter, then we automatically get a clean way to find an element:

makeNamedGridElements[gridEls_, tag_String] :=
 MapIndexed[
  FENamed[#, tag <> Map[ToString, #2]] &,
  gridEls,
  {2}
  ]

realGrid =
 Grid@makeNamedGridElements[#, "realGrid_"] &@
  Partition[
   Thread@
    Graphics[
     Thread[{RandomColor[25], Disk[]}],
     ImageSize -> 25
     ], 
   5]

enter image description here

Once we have the current element it's easy to increment to the next, but finding the current one is admittedly beyond my ken right now.

$\endgroup$
  • $\begingroup$ This is quite elaborate and it will take some time to understand how this works. I am not sure I will be able to apply it to what I had in mind, but I appreciate your work on this! $\endgroup$ – Mr.Wizard Feb 25 at 10:23
  • $\begingroup$ @Mr.Wizard no worries if you can’t. My idea was simply to provide a tag to each grid element (via BoxID) and then use some undocumented front-end functions to find those elements (these things can also replace them, set/get Options on them and much much more). $\endgroup$ – b3m2a1 Feb 25 at 10:25
  • $\begingroup$ Is FENaming.wl something you previously produced? I do not recall its appearance. $\endgroup$ – Mr.Wizard Feb 25 at 10:29
  • 1
    $\begingroup$ @Mr.Wizard the core code has existed since I wrote that answer, but I just recently cleaned it up and made it into a proper package so that I could load it in this answe. $\endgroup$ – b3m2a1 Feb 25 at 10:30
3
$\begingroup$

Perhaps something crude like this could work:

nb = EvaluationNotebook[];
GridBox[{{"a", "b", "c"}, {"d", "e", "f"}}] // DisplayForm

(* select col 2, row 2 -> 5 *)
SelectionMove[nb, Previous, Cell, 2];
SelectionMove[nb, Before, CellContents]
Do[SelectionMove[nb, Next, Character, 2], 5]
SelectionMove[nb, All, Word, 1]

enter image description here

$\endgroup$
  • $\begingroup$ This seems to only work with single character grid elements. If I use {"a", "b00", "c111"} for the first row I get a different selection. $\endgroup$ – Mr.Wizard Feb 25 at 10:18
  • 1
    $\begingroup$ @Mr.Wizard After looking at it again, I don't think this approach is tenable. $\endgroup$ – M.R. Feb 27 at 23:21

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.