Class MenuTreeBuilder

java.lang.Object
com.vaadin.swingbridge.surrogates.internal.MenuTreeBuilder

public final class MenuTreeBuilder extends Object
Shared tree-rebuild engine for the menu surrogates (SD_sjpopupmenu). Both SJMenuBar (peer = Vaadin MenuBar) and SJPopupMenu (peer = Vaadin ContextMenu) translate the emulator-pushed MenuNode tree into a Vaadin MenuItem tree the same way: MenuBar, ContextMenu, and SubMenu all implement HasMenuItems (addItem(String|Component, listener) -> MenuItem, items carry getSubMenu() -> SubMenu), so the walk is byte-identical above the peer type.

The one asymmetry is the top-level separator: addSeparator() lives on ContextMenuBase / SubMenuBase, not on HasMenuItems. MenuBar has no top-level addSeparator (SD_sjmenubar drops + WARNs); ContextMenu does (SD_sjpopupmenu renders it). So rebuild(HasMenuItems, List, Consumer) takes a per-caller topSeparator callback — the caller supplies "WARN + skip" or "ctx.addSeparator()". Sub-menu separators are uniform (SubMenu.addSeparator() is always present) and handled inline.

A push that changes only checked states, click handlers or accelerators is applied to the existing items by patchInPlace(List, List, List) rather than rebuilt. A checkable item's click pushes a new tree in the same round-trip, and a rebuild would detach the item Vaadin is toggling: Vaadin's queued checked-state update for it then throws in the browser (vaadin/flow-components#10227; SD_menu_patch_in_place).

Stateless static utility — all per-component state (accelerator registrations, current tree, re-entrancy guard) lives in the caller's MenuBarStateStore.

  • Method Summary

    Modifier and Type
    Method
    Description
    static com.vaadin.flow.component.Component
    buildIconAndText(com.vaadin.flow.component.Component icon, String text)
    Build a Span with the icon first and the text second so Vaadin's addItem(Component, ...) renders both.
    static void
    installAccelerators(com.vaadin.flow.component.UI ui, com.vaadin.flow.component.Component owner, Map<MenuNode, com.vaadin.flow.component.contextmenu.MenuItem> leafToPeer, Map<KeyStroke, com.vaadin.flow.shared.Registration> registrations)
    Install a UI-scoped Vaadin shortcut for every leaf node carrying an accelerator.
    static boolean
    patchInPlace(List<MenuNode> current, List<MenuNode> next, List<com.vaadin.flow.component.contextmenu.MenuItem> peers)
    Applies next to the items current built, without rebuilding, when the two trees differ only in checked states, click handlers or accelerators.
    static void
    rebuild(com.vaadin.flow.component.contextmenu.HasMenuItems root, List<MenuNode> tree, Consumer<MenuNode> topSeparator)
    Walk tree into root.
    static void
    recoverLeafMap(List<MenuNode> nodes, List<com.vaadin.flow.component.contextmenu.MenuItem> peers, Map<MenuNode, com.vaadin.flow.component.contextmenu.MenuItem> map)
    Walk parallel JDK MenuNode and Vaadin MenuItem trees to recover the leaf-to-peer map after attach (the original rebuild's MenuItem references aren't addressable later).

    Methods inherited from class Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Method Details

    • rebuild

      public static void rebuild(com.vaadin.flow.component.contextmenu.HasMenuItems root, List<MenuNode> tree, Consumer<MenuNode> topSeparator)
      Walk tree into root. Separator nodes at the top level are delegated to topSeparator; all other nodes build via addNode(HasMenuItems, MenuNode). The caller recovers the accelerator map afterwards with recoverLeafMap(List, List, Map), the same walk the in-place path uses.
    • patchInPlace

      public static boolean patchInPlace(List<MenuNode> current, List<MenuNode> next, List<com.vaadin.flow.component.contextmenu.MenuItem> peers)
      Applies next to the items current built, without rebuilding, when the two trees differ only in checked states, click handlers or accelerators. Checkable items get setChecked; every item is rebound to its new handler. The caller reinstalls accelerators.

      Text and enabled stay on the rebuild path: a MenuBar re-renders its root buttons for theme, class and tooltip changes only (MenuBarRootItem), so an in-place write there would leave the button stale. Icons compare by identity, which holds only because the emulator reuses its converted icon while the Swing Icon is unchanged.

      Parameters:
      peers - the root's items, as its getItems() returns them
      Returns:
      false, with nothing touched, when anything else differs — text, icon, enabled, checkable, or the shape — and the caller must rebuild
    • buildIconAndText

      public static com.vaadin.flow.component.Component buildIconAndText(com.vaadin.flow.component.Component icon, String text)
      Build a Span with the icon first and the text second so Vaadin's addItem(Component, ...) renders both. Mirrors SJLabel / SJButton's icon+text composition per SD_sjlabel.
    • installAccelerators

      public static void installAccelerators(com.vaadin.flow.component.UI ui, com.vaadin.flow.component.Component owner, Map<MenuNode, com.vaadin.flow.component.contextmenu.MenuItem> leafToPeer, Map<KeyStroke, com.vaadin.flow.shared.Registration> registrations)
      Install a UI-scoped Vaadin shortcut for every leaf node carrying an accelerator. Each shortcut fires the leaf's onClick directly (same path as a click on the item). Resulting Registrations are stored in registrations keyed by the JDK KeyStroke so the next rebuild can tear them down. Unmappable keystrokes log via SHelper.onUnimplemented(Object, String, Object...) and skip browser install.
    • recoverLeafMap

      public static void recoverLeafMap(List<MenuNode> nodes, List<com.vaadin.flow.component.contextmenu.MenuItem> peers, Map<MenuNode, com.vaadin.flow.component.contextmenu.MenuItem> map)
      Walk parallel JDK MenuNode and Vaadin MenuItem trees to recover the leaf-to-peer map after attach (the original rebuild's MenuItem references aren't addressable later). Both trees are isomorphic since the same rebuild built them. Separator nodes never produce a MenuItem (top-level MenuBar drops them; ContextMenu / SubMenu insert an <hr> that doesn't appear in getItems()), so skip them without stepping the peer index in both cases.