5

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?

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371

2 Answers2

4

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.

b3m2a1
  • 46,870
  • 3
  • 92
  • 239
  • 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! – Mr.Wizard Feb 25 '19 at 10:23
  • @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). – b3m2a1 Feb 25 '19 at 10:25
  • Is FENaming.wl something you previously produced? I do not recall its appearance. – Mr.Wizard Feb 25 '19 at 10:29
  • 1
    @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. – b3m2a1 Feb 25 '19 at 10:30
3

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

M.R.
  • 31,425
  • 8
  • 90
  • 281
  • 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. – Mr.Wizard Feb 25 '19 at 10:18
  • 1
    @Mr.Wizard After looking at it again, I don't think this approach is tenable. – M.R. Feb 27 '19 at 23:21