UIForAnyWindow.java
package swingtree;
import org.jspecify.annotations.Nullable;
import sprouts.Val;
import swingtree.input.Keyboard;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Optional;
import java.util.function.Consumer;
/**
* A SwingTree builder node for configuring any kind of {@link Window} type.
* Take a look at the {@link UIForJDialog} and {@link UIForJFrame} classes,
* which are specialized subtypes of this class.
*
* @param <I> The type of the builder itself.
* @param <W> The type of the window which is being configured by this builder.
*/
public abstract class UIForAnyWindow<I extends UIForAnyWindow<I,W>, W extends Window> extends UIForAnything<I,W,Component>
{
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UIForAnyWindow.class);
/**
* Adds a title to the window. <br>
* Note that the way this is displayed depends on the window type and the
* operating system.
*
* @param title The title to be shown in the top bar of the window.
* @return This builder.
*/
public final I withTitle( String title ) {
return _with( thisWindow -> {
_setTitleOf( thisWindow, title );
})
._this();
}
/**
* Binds a text property to the window determining the title displayed in the top bar of the window. <br>
* Note that the way this is displayed depends on the window type and the
* operating system.
*
* @param title The title property whose text will be shown in the top bar of the window.
* @return This builder.
*/
public final I withTitle( Val<String> title ) {
NullUtil.nullArgCheck(title, "title", Val.class);
NullUtil.nullPropertyCheck(title, "title");
return _withOnShow( title, (thisWindow,v) -> {
_setTitleOf(thisWindow, v);
})
._with( thisWindow -> {
_setTitleOf( thisWindow, title.orElseThrow() );
})
._this();
}
/**
* Sets the {@link UI.OnWindowClose} operation for the window. <br>
* This translates to {@link JFrame#setDefaultCloseOperation(int)} or
* {@link JDialog#setDefaultCloseOperation(int)} depending on the window type.
* The following operations are supported:
* <ul>
* <li>{@link UI.OnWindowClose#DO_NOTHING} - Do nothing when the window is closed.</li>
* <li>{@link UI.OnWindowClose#HIDE} - Hide the window when it is closed.</li>
* <li>{@link UI.OnWindowClose#DISPOSE} - Dispose the window when it is closed.</li>
* </ul>
* @param onClose The operation to be executed when the window is closed.
* @return This declarative builder instance to enable method chaining.
*/
public final I withOnCloseOperation(UI.OnWindowClose onClose ) {
NullUtil.nullArgCheck(onClose, "onClose", UI.OnWindowClose.class);
return _with( thisWindow -> {
if ( thisWindow instanceof JFrame )
((JFrame)thisWindow).setDefaultCloseOperation(onClose.forSwing());
else if ( thisWindow instanceof JDialog )
((JDialog)thisWindow).setDefaultCloseOperation(onClose.forSwing());
else
log.warn("Cannot set close operation on window of type: {}", thisWindow.getClass().getName());
})
._this();
}
/**
* Makes the window visible in the center of the screen.
*/
public abstract void show();
protected abstract Optional<JRootPane> _getRootPaneOf(W thisWindow);
protected abstract void _setTitleOf( W thisWindow, String title );
private void _onKeyStroke( int code, Consumer<ActionEvent> action, W thisWindow ) {
_getRootPaneOf(thisWindow).ifPresent(rootPane -> {
KeyStroke k = KeyStroke.getKeyStroke(code, 0);
int w = JComponent.WHEN_IN_FOCUSED_WINDOW;
rootPane.registerKeyboardAction(action::accept, k, w);
});
}
/**
* Adds the supplied {@link sprouts.Action} wrapped in a {@link KeyListener} to the component,
* to receive key events triggered when the wrapped component receives a particular
* keyboard input matching the provided {@link swingtree.input.Keyboard.Key}.
* <br><br>
* @param key The {@link swingtree.input.Keyboard.Key} which should be matched to the key event.
* @param onKeyPressed The {@link sprouts.Action} which will be executed once the wrapped component received the targeted key press.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onPressed( Keyboard.Key key, sprouts.Action<WindowDelegate<W, ActionEvent>> onKeyPressed ) {
NullUtil.nullArgCheck(key, "key", Keyboard.Key.class);
NullUtil.nullArgCheck(onKeyPressed, "onKeyPressed", sprouts.Action.class);
return _with( thisWindow -> {
_onKeyStroke( key.code, e -> onKeyPressed.accept(_createDelegate(thisWindow, null)), thisWindow );
})
._this();
}
/**
* Adds the supplied {@link sprouts.Action} wrapped in a {@link java.awt.event.FocusListener}
* to the component, to receive those focus events where the wrapped component gains input focus.
*
* @param onFocus The {@link sprouts.Action} which should be executed once the input focus was gained on the wrapped component.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onFocusGain( sprouts.Action<WindowDelegate<W, FocusEvent>> onFocus ) {
NullUtil.nullArgCheck(onFocus, "onFocus", sprouts.Action.class);
return _with( thisWindow -> {
thisWindow.addFocusListener(new FocusAdapter() {
@Override public void focusGained(FocusEvent e) {
_runInApp(()->onFocus.accept(_createDelegate(thisWindow, e)));
}
});
})
._this();
}
/**
* Adds the supplied {@link sprouts.Action} wrapped in a focus listener
* to receive those focus events where the wrapped component loses input focus.
*
* @param onFocus The {@link sprouts.Action} which should be executed once the input focus was lost on the wrapped component.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onFocusLoss( sprouts.Action<WindowDelegate<W, FocusEvent>> onFocus ) {
NullUtil.nullArgCheck(onFocus, "onFocus", Action.class);
return _with( thisWindow -> {
thisWindow.addFocusListener(new FocusAdapter() {
@Override public void focusLost(FocusEvent e) {
_runInApp(()->onFocus.accept(_createDelegate(thisWindow, e)));
}
});
})
._this();
}
/**
* Adds the supplied {@link sprouts.Action} wrapped in a {@link java.awt.event.WindowListener}
* to the component, to receive {@link WindowListener#windowClosing(WindowEvent)} events
* which are invoked when a window is in the process of being closed.
* The close operation can be overridden at this point (see {@link JFrame#DO_NOTHING_ON_CLOSE}). <br>
* Note that this kind of event is typically triggered when the user clicks
* the close button in the top bar of the window.
*
* @param onClose The {@link sprouts.Action} which should be invoked when the wrapped component is in the process of being closed.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onClose( sprouts.Action<WindowDelegate<W, WindowEvent>> onClose ) {
NullUtil.nullArgCheck(onClose, "onClose", Action.class);
return _with( thisWindow -> {
thisWindow.addWindowListener(new WindowAdapter() {
@Override public void windowClosing( WindowEvent e ) {
_runInApp(()->onClose.accept(_createDelegate(thisWindow, e)));
}
});
})
._this();
}
/**
* Adds the supplied {@link sprouts.Action} wrapped in a {@link java.awt.event.WindowListener}
* to the component, to receive {@link WindowListener#windowClosed(WindowEvent)} events
* which are invoked when a window has been closed. <br>
* Note that this kind of event is typically triggered when the user clicks
* the close button in the top bar of the window.
*
* @param onClose The {@link sprouts.Action} which should be invoked when the wrapped component has been closed.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onClosed( sprouts.Action<WindowDelegate<W, WindowEvent>> onClose ) {
NullUtil.nullArgCheck(onClose, "onClose", Action.class);
return _with( thisWindow -> {
thisWindow.addWindowListener(new WindowAdapter() {
@Override public void windowClosed( WindowEvent e ) {
_runInApp(()->onClose.accept(_createDelegate(thisWindow, e)));
}
});
})
._this();
}
/**
* Adds the supplied {@link sprouts.Action} wrapped in a {@link java.awt.event.WindowListener}
* to the component, to receive {@link WindowListener#windowOpened(WindowEvent)} events
* which are invoked when a window has been opened. <br>
* Note that this kind of event is typically triggered when the user clicks
* the close button in the top bar of the window.
*
* @param onOpen The {@link sprouts.Action} which should be invoked when the wrapped component has been opened.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onOpened( sprouts.Action<WindowDelegate<W, WindowEvent>> onOpen ) {
NullUtil.nullArgCheck(onOpen, "onOpen", Action.class);
return _with( thisWindow -> {
thisWindow.addWindowListener(new WindowAdapter() {
@Override public void windowOpened( WindowEvent e ) {
_runInApp(()->onOpen.accept(_createDelegate(thisWindow, e)));
}
});
})
._this();
}
/**
* Adds the supplied {@link sprouts.Action} wrapped in a {@link java.awt.event.WindowListener}
* to the component, to receive {@link WindowListener#windowIconified(WindowEvent)} events
* which are invoked when a window is changed from a normal to a minimized state.
* For many platforms, a minimized window is displayed as the icon
* specified in the window's iconImage property.
* <br>
* Minification is usually triggered when the user clicks the minimize button
* in the top bar of the window. But this depends on the operating system.
*
* @param onIconify The {@link sprouts.Action} which should be invoked when the wrapped component has been iconified.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onIconified( sprouts.Action<WindowDelegate<W, WindowEvent>> onIconify ) {
NullUtil.nullArgCheck(onIconify, "onIconify", Action.class);
return _with( thisWindow -> {
thisWindow.addWindowListener(new WindowAdapter() {
@Override public void windowIconified( WindowEvent e ) {
_runInApp(()->onIconify.accept(_createDelegate(thisWindow, e)));
}
});
})
._this();
}
/**
* Adds the supplied {@link sprouts.Action} wrapped in a {@link java.awt.event.WindowListener}
* to the component, to receive {@link WindowListener#windowDeiconified(WindowEvent)} events
* which are invoked when a window is changed from a minimized
* to a normal state, usually by the user restoring it from the task bar.
*
* @param onDeiconify The {@link sprouts.Action} which should be invoked when the wrapped component has been deiconified.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onDeiconified( sprouts.Action<WindowDelegate<W, WindowEvent>> onDeiconify ) {
NullUtil.nullArgCheck(onDeiconify, "onDeiconify", Action.class);
return _with( thisWindow -> {
thisWindow.addWindowListener(new WindowAdapter() {
@Override public void windowDeiconified( WindowEvent e ) {
_runInApp(()->onDeiconify.accept(_createDelegate(thisWindow, e)));
}
});
})
._this();
}
/**
* Adds the supplied {@link sprouts.Action} wrapped in a {@link java.awt.event.WindowListener}
* to the component, to receive {@link WindowListener#windowActivated(WindowEvent)} events
* which are invoked when the Window is set to be the active Window.
* Only a Frame or a Dialog can be the active Window.
* The native windowing system may denote the active Window or
* its children with special decorations, such as a highlighted title bar.
* The active Window is always either the focused Window,
* or the first Frame or Dialog that is an owner of the focused Window.
* So this kind of event is usually triggered when the user makes the window active
* by clicking it.
*
* @param onActivate The {@link sprouts.Action} which should be invoked when the wrapped component has been activated.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onActivated( sprouts.Action<WindowDelegate<W, WindowEvent>> onActivate ) {
NullUtil.nullArgCheck(onActivate, "onActivate", Action.class);
return _with( thisWindow -> {
thisWindow.addWindowListener(new WindowAdapter() {
@Override public void windowActivated( WindowEvent e ) {
_runInApp(()->onActivate.accept(_createDelegate(thisWindow, e)));
}
});
})
._this();
}
/**
* Adds the supplied {@link sprouts.Action} wrapped in a {@link java.awt.event.WindowListener}
* to the component, to receive {@link WindowListener#windowDeactivated(WindowEvent)} events
* which are invoked when a Window is no longer the active Window. Only a Frame or a
* Dialog can be the active Window. The native windowing system may denote
* the active Window or its children with special decorations, such as a
* highlighted title bar. The active Window is always either the focused
* Window, or the first Frame or Dialog that is an owner of the focused
* Window.
* This kind of event typically occurs when the user clicks another window
* in the task bar of the operating system.
*
* @param onDeactivate The {@link sprouts.Action} which should be invoked when the wrapped component has been deactivated.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onDeactivated( sprouts.Action<WindowDelegate<W, WindowEvent>> onDeactivate ) {
NullUtil.nullArgCheck(onDeactivate, "onDeactivate", Action.class);
return _with( thisWindow -> {
thisWindow.addWindowListener(new WindowAdapter() {
@Override public void windowDeactivated( WindowEvent e ) {
_runInApp(()->onDeactivate.accept(_createDelegate(thisWindow, e)));
}
});
})
._this();
}
/**
* Adds the supplied {@link sprouts.Action} wrapped in a {@link java.awt.event.WindowListener}
* to the component, to receive {@link WindowStateListener#windowStateChanged(WindowEvent)} events
* which are invoked when a window has been changed. <br>
* Note that this kind of event is typically invoked when the window is
* iconified, minimized, maximized or restored.
*
* @param onStateChanged The {@link sprouts.Action} which should be invoked when the wrapped component has been changed.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onStateChanged( sprouts.Action<WindowDelegate<W, WindowEvent>> onStateChanged ) {
NullUtil.nullArgCheck(onStateChanged, "onStateChanged", Action.class);
return _with( thisWindow -> {
thisWindow.addWindowListener(new WindowAdapter() {
@Override public void windowStateChanged( WindowEvent e ) {
_runInApp(()->onStateChanged.accept(_createDelegate(thisWindow, e)));
}
});
})
._this();
}
/**
* Adds the supplied {@link sprouts.Action} wrapped in a {@link java.awt.event.WindowListener}
* to the component, to receive {@link WindowFocusListener#windowGainedFocus(WindowEvent)} events
* which are invoked when the window is set to be gaining input focus, which means
* that the Window, or one of its subcomponents, will receive keyboard
* events.
* This event is typically triggered when the user clicks the window.
*
* @param onFocusGained The {@link sprouts.Action} which should be invoked when the wrapped component has gained input focus.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onInputFocusGained( sprouts.Action<WindowDelegate<W, WindowEvent>> onFocusGained ) {
NullUtil.nullArgCheck(onFocusGained, "onFocusGained", Action.class);
return _with( thisWindow -> {
thisWindow.addWindowFocusListener(new WindowFocusListener() {
@Override public void windowGainedFocus( WindowEvent e ) {
_runInApp(()->onFocusGained.accept(_createDelegate(thisWindow, e)));
}
@Override public void windowLostFocus( WindowEvent e ) {}
});
})
._this();
}
/**
* Adds the supplied {@link sprouts.Action} wrapped in a {@link java.awt.event.WindowListener}
* to the component, to receive {@link WindowFocusListener#windowLostFocus(WindowEvent)} events
* which are invoked when the window is set to be losing input focus, which means
* that input focus is being transferred to another Window or no Window at all and
* that keyboard events will no longer be delivered to the Window or any of
* its subcomponents.
*
* @param onFocusLost The {@link sprouts.Action} which should be invoked when the wrapped component has lost input focus.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onInputFocusLost( sprouts.Action<WindowDelegate<W, WindowEvent>> onFocusLost ) {
NullUtil.nullArgCheck(onFocusLost, "onFocusLost", Action.class);
return _with( thisWindow -> {
thisWindow.addWindowFocusListener(new WindowFocusListener() {
@Override public void windowGainedFocus( WindowEvent e ) {}
@Override public void windowLostFocus( WindowEvent e ) {
_runInApp(()->onFocusLost.accept(_createDelegate(thisWindow, e)));
}
});
})
._this();
}
private <E> WindowDelegate<W, E> _createDelegate( W window, @Nullable E event ) {
return new WindowDelegate<W, E>() {
@Override public W get() { return window; }
@Override public @Nullable E getEvent() { return event; }
};
}
}