UIForPopup.java

package swingtree;

import sprouts.Action;
import sprouts.Val;

import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.*;
import java.util.Objects;
import java.util.function.Consumer;

/**
 *  A SwingTree builder node designed for configuring {@link JPopupMenu} instances.
 * 	<p>
 * 	<b>Take a look at the <a href="https://globaltcad.github.io/swing-tree/">living swing-tree documentation</a>
 * 	where you can browse a large collection of examples demonstrating how to use the API of this class or other classes.</b>
 */
public final class UIForPopup<P extends JPopupMenu> extends UIForAnySwing<UIForPopup<P>, P>
{
    private final BuilderState<P> _state;

    UIForPopup( BuilderState<P> state ) {
        Objects.requireNonNull(state);
        _state = state;
    }

    @Override
    protected BuilderState<P> _state() {
        return _state;
    }
    
    @Override
    protected UIForPopup<P> _newBuilderWithState(BuilderState<P> newState ) {
        return new UIForPopup<>(newState);
    }

    /**
     *  Determines if the border is painted or not.
     *
     * @param borderPainted True if the border is painted, false otherwise
     * @return This builder node, to qllow for method chaining.
     */
    public final UIForPopup<P> borderIsPaintedIf( boolean borderPainted ) {
        return _with( thisComponent -> {
                    thisComponent.setBorderPainted( borderPainted );
                })
                ._this();
    }

    /**
     *  Determines if the border is painted or not
     *  based on the value of the given {@link Val}.
     *  If the value of the {@link Val} changes, the border will be painted or not.
     *
     * @param isPainted A {@link Val} which will be used to determine if the border is painted or not.
     * @return This builder node, to qllow for method chaining.
     */
    public final UIForPopup<P> borderIsPaintedIf( Val<Boolean> isPainted ) {
        return _withOnShow( isPainted, (thisComponent,it) -> {
                    thisComponent.setBorderPainted( it );
                })
                ._with( thisComponent -> {
                    thisComponent.setBorderPainted( isPainted.get() );
                })
                ._this();
    }

    /**
     *  Registers a listener to be notified when the popup is shown.
     *  This is typically triggered when {@link JPopupMenu#show(Component, int, int)} is called.
     *
     * @param action The action to be executed when the popup is shown.
     * @return this
     */
    public UIForPopup<P> onVisible( Action<ComponentDelegate<P, PopupMenuEvent>> action ) {
        NullUtil.nullArgCheck(action, "action", Action.class);
        return _with( thisComponent -> {
                    _onPopupOpen(thisComponent,
                        e -> _runInApp(()->action.accept(new ComponentDelegate<>( thisComponent, e )) )
                    );
                })
                ._this();
    }

    private void _onPopupOpen( P thisComponent, Consumer<PopupMenuEvent> consumer ) {
        thisComponent.addPopupMenuListener(new PopupMenuListener() {
            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                // This method is called before the popup menu becomes visible.
                consumer.accept(e);
            }
            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {/* Not relevant here */}
            @Override
            public void popupMenuCanceled(PopupMenuEvent e) {/* Not relevant here */}
        });
    }

    /**
     *  Registers a listener to be notified when the popup becomes invisible,
     *  meaning its popup menu is hidden.
     *
     * @param action The action to be executed when the popup becomes invisible.
     * @return This builder node, to allow for method chaining.
     */
    public UIForPopup<P> onInvisible( Action<ComponentDelegate<P, PopupMenuEvent>> action ) {
        NullUtil.nullArgCheck(action, "action", Action.class);
        return _with( thisComponent -> {
                    _onPopupClose(thisComponent,
                        e -> _runInApp(()->action.accept(new ComponentDelegate<>( (P) thisComponent, e )) )
                    );
                })
                ._this();
    }

    private void _onPopupClose( P thisComponent, Consumer<PopupMenuEvent> consumer ) {
        thisComponent.addPopupMenuListener(new PopupMenuListener() {
            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {/* Not relevant here */}
            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                consumer.accept(e); // This method is called before the popup menu becomes invisible
            }
            @Override
            public void popupMenuCanceled(PopupMenuEvent e) {/* Not relevant here */}
        });
    }

    /**
     *  Registers a listener to be notified when the popup is canceled.
     *  This is typically triggered when the user clicks outside the popup.
     *
     * @param action the action to be executed when the popup is canceled.
     * @return this
     */
    public UIForPopup<P> onCancel( Action<ComponentDelegate<P, PopupMenuEvent>> action ) {
        NullUtil.nullArgCheck(action, "action", Action.class);
        return _with( thisComponent -> {
                    _onPopupCancel(thisComponent,
                        e -> _runInApp(()->action.accept(new ComponentDelegate<>( thisComponent, e )) )
                    );
                })
                ._this();
    }

    private void _onPopupCancel( P thisComponent, Consumer<PopupMenuEvent> consumer ) {
        thisComponent.addPopupMenuListener(new PopupMenuListener() {
            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {/* Not relevant here */}
            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {/* Not relevant here */}
            @Override
            public void popupMenuCanceled(PopupMenuEvent e) {
                consumer.accept(e); // This method is called when the popup menu is canceled
            }
        });
    }


    public UIForPopup<P> add(JMenuItem item) { return this.add(UI.of(item)); }

    public UIForPopup<P> add(JSeparator separator) { return this.add(UI.of(separator)); }

    public UIForPopup<P> add(JPanel panel) { return this.add(UI.of(panel)); }
}