VAqua.src.org.violetlib.aqua.AquaSpinnerUI Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vaqua Show documentation
Show all versions of vaqua Show documentation
An improved native Swing look and feel for macOS
The newest version!
/*
* Changes Copyright (c) 2015-2023 Alan Snyder.
* All rights reserved.
*
* You may not use, copy or modify this file, except in compliance with the license agreement. For details see
* accompanying license terms.
*/
/*
* Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.violetlib.aqua;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.*;
import java.text.AttributedCharacterIterator.Attribute;
import java.text.Format.Field;
import java.util.Calendar;
import java.util.Map;
import javax.swing.*;
import javax.swing.JSpinner.DefaultEditor;
import javax.swing.plaf.ActionMapUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.SpinnerUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicButtonUI;
import javax.swing.text.InternationalFormatter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.violetlib.aqua.AquaUtils.RecyclableSingleton;
import org.violetlib.aqua.AquaUtils.RecyclableSingletonFromDefaultConstructor;
import org.violetlib.jnr.LayoutInfo;
import org.violetlib.jnr.aqua.AquaUIPainter;
import org.violetlib.jnr.aqua.AquaUIPainter.Size;
import org.violetlib.jnr.aqua.AquaUIPainter.State;
import org.violetlib.jnr.aqua.Configuration;
import org.violetlib.jnr.aqua.SpinnerArrowsConfiguration;
import org.violetlib.jnr.aqua.SpinnerArrowsLayoutConfiguration;
/**
* This is originally derived from BasicSpinnerUI, but they made everything private
* so we can't subclass!
*/
public class AquaSpinnerUI extends SpinnerUI implements AquaComponentUI,
SystemPropertyChangeManager.SystemPropertyChangeListener {
private static final RecyclableSingleton extends PropertyChangeListener> propertyChangeListener = new RecyclableSingletonFromDefaultConstructor(PropertyChangeHandler.class);
static PropertyChangeListener getPropertyChangeListener() {
return propertyChangeListener.get();
}
private static final RecyclableSingleton nextButtonHandler = new RecyclableSingleton() {
@Override
protected ArrowButtonHandler getInstance() {
return new ArrowButtonHandler("increment", true);
}
};
static ArrowButtonHandler getNextButtonHandler() {
return nextButtonHandler.get();
}
private static final RecyclableSingleton previousButtonHandler = new RecyclableSingleton() {
@Override
protected ArrowButtonHandler getInstance() {
return new ArrowButtonHandler("decrement", false);
}
};
static ArrowButtonHandler getPreviousButtonHandler() {
return previousButtonHandler.get();
}
protected JSpinner spinner;
protected SpinPainter spinPainter;
protected final AquaUIPainter painter = AquaPainting.create();
protected final static int SEPARATION = 3;
public static ComponentUI createUI(JComponent c) {
return new AquaSpinnerUI();
}
private void maybeAdd(Component c, String s) {
if (c != null) {
spinner.add(c, s);
}
}
public void installUI(JComponent c) {
this.spinner = (JSpinner)c;
installDefaults();
installListeners();
TransparentButton next = createNextButton();
TransparentButton prev = createPreviousButton();
spinPainter = new SpinPainter(next, prev);
maybeAdd(next, "Next");
maybeAdd(prev, "Previous");
maybeAdd(createEditor(), "Editor");
maybeAdd(spinPainter, "Painter");
updateEnabledState();
installKeyboardActions();
LookAndFeel.installProperty(spinner, "opaque", false);
}
public void uninstallUI(JComponent c) {
uninstallDefaults();
uninstallListeners();
spinner = null;
c.removeAll();
}
protected void installDefaults() {
spinner.setLayout(createLayout());
LookAndFeel.installBorder(spinner, "Spinner.border");
AquaUtils.installFont(spinner, "Spinner.font");
configureFocusable(spinner);
}
protected void uninstallDefaults() {
spinner.setLayout(null);
}
protected void installListeners() {
spinner.addPropertyChangeListener(getPropertyChangeListener());
}
protected void uninstallListeners() {
spinner.removePropertyChangeListener(getPropertyChangeListener());
}
protected LayoutManager createLayout() {
return new SpinnerLayout();
}
protected PropertyChangeListener createPropertyChangeListener() {
return new PropertyChangeHandler();
}
@Override
public void systemPropertyChanged(JComponent c, Object type) {
if (type.equals(OSXSystemProperties.USER_PREFERENCE_CHANGE_TYPE)) {
configureFocusable(c);
}
}
private void configureFocusable(JComponent c) {
boolean isFocusable = OSXSystemProperties.isFullKeyboardAccessEnabled();
c.setFocusable(isFocusable);
}
@Override
public void appearanceChanged(@NotNull JComponent c, @NotNull AquaAppearance appearance) {
}
@Override
public void activeStateChanged(@NotNull JComponent c, boolean isActive) {
}
@Override
public void update(Graphics g, JComponent c) {
AppearanceManager.registerCurrentAppearance(c);
super.update(g, c);
}
protected TransparentButton createPreviousButton() {
TransparentButton b = new TransparentButton();
b.addActionListener(getPreviousButtonHandler());
b.addMouseListener(getPreviousButtonHandler());
b.setInheritsPopupMenu(true);
return b;
}
protected TransparentButton createNextButton() {
TransparentButton b = new TransparentButton();
b.addActionListener(getNextButtonHandler());
b.addMouseListener(getNextButtonHandler());
b.setInheritsPopupMenu(true);
return b;
}
/**
* {@inheritDoc}
*/
public int getBaseline(JComponent c, int width, int height) {
super.getBaseline(c, width, height);
JComponent editor = spinner.getEditor();
Insets insets = spinner.getInsets();
width = width - insets.left - insets.right;
height = height - insets.top - insets.bottom;
if (width >= 0 && height >= 0) {
int baseline = editor.getBaseline(width, height);
if (baseline >= 0) {
return insets.top + baseline;
}
}
return -1;
}
/**
* {@inheritDoc}
*/
public Component.BaselineResizeBehavior getBaselineResizeBehavior(
JComponent c) {
super.getBaselineResizeBehavior(c);
return spinner.getEditor().getBaselineResizeBehavior();
}
@SuppressWarnings("serial") // Superclass is not serializable across versions
class TransparentButton extends JButton implements SwingConstants {
boolean interceptRepaints = false;
public TransparentButton() {
super();
setUI(new BasicButtonUI()); // avoid becoming focusable when Full Keyboard Access is enabled
setFocusable(false);
// only intercept repaints if we are after this has been initialized
// otherwise we can't talk to our containing class
interceptRepaints = true;
}
public void paint(Graphics g) {}
public void repaint() {
// only intercept repaints if we are after this has been initialized
// otherwise we can't talk to our containing class
if (interceptRepaints) {
if (spinPainter == null) return;
spinPainter.repaint();
}
super.repaint();
}
}
protected JComponent createEditor() {
JComponent editor = spinner.getEditor();
fixupEditor(editor);
return editor;
}
protected void replaceEditor(JComponent oldEditor, JComponent newEditor) {
spinner.remove(oldEditor);
fixupEditor(newEditor);
spinner.add(newEditor, "Editor");
}
protected void fixupEditor(JComponent editor) {
if (!(editor instanceof DefaultEditor)) return;
editor.setOpaque(false);
editor.setInheritsPopupMenu(true);
if (editor.getFont() instanceof UIResource) {
editor.setFont(spinner.getFont());
}
JFormattedTextField editorTextField = ((DefaultEditor)editor).getTextField();
if (editorTextField.getFont() instanceof UIResource) {
editorTextField.setFont(spinner.getFont());
}
InputMap spinnerInputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
InputMap editorInputMap = editorTextField.getInputMap();
KeyStroke[] keys = spinnerInputMap.keys();
for (KeyStroke k : keys) {
editorInputMap.put(k, spinnerInputMap.get(k));
}
}
void updateEnabledState() {
updateEnabledState(spinner, spinner.isEnabled());
}
private void updateEnabledState(Container c, boolean enabled) {
for (int counter = c.getComponentCount() - 1; counter >= 0; counter--) {
Component child = c.getComponent(counter);
child.setEnabled(enabled);
if (child instanceof Container) {
updateEnabledState((Container)child, enabled);
}
}
}
private void installKeyboardActions() {
InputMap iMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
SwingUtilities.replaceUIInputMap(spinner, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, iMap);
SwingUtilities.replaceUIActionMap(spinner, getActionMap());
}
private InputMap getInputMap(int condition) {
if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
return (InputMap)UIManager.get("Spinner.ancestorInputMap");
}
return null;
}
private ActionMap getActionMap() {
ActionMap map = (ActionMap)UIManager.get("Spinner.actionMap");
if (map == null) {
map = createActionMap();
if (map != null) {
UIManager.getLookAndFeelDefaults().put("Spinner.actionMap", map);
}
}
return map;
}
private ActionMap createActionMap() {
ActionMap map = new ActionMapUIResource();
map.put("increment", getNextButtonHandler());
map.put("decrement", getPreviousButtonHandler());
return map;
}
@SuppressWarnings("serial") // Superclass is not serializable across versions
private static class ArrowButtonHandler extends AbstractAction implements MouseListener {
private final javax.swing.Timer autoRepeatTimer;
private final boolean isNext;
private JSpinner activeSpinner = null;
ArrowButtonHandler(String name, boolean isNext) {
super(name);
this.isNext = isNext;
autoRepeatTimer = new javax.swing.Timer(60, this);
autoRepeatTimer.setInitialDelay(300);
}
private JSpinner eventToSpinner(AWTEvent e) {
Object src = e.getSource();
while ((src instanceof Component) && !(src instanceof JSpinner)) {
src = ((Component)src).getParent();
}
return (src instanceof JSpinner) ? (JSpinner)src : null;
}
public void actionPerformed(ActionEvent e) {
if (!(e.getSource() instanceof javax.swing.Timer)) {
// Most likely resulting from being in ActionMap.
activeSpinner = eventToSpinner(e);
}
if (activeSpinner != null) {
try {
int calendarField = getCalendarField(activeSpinner);
activeSpinner.commitEdit();
if (calendarField != -1) {
((SpinnerDateModel) activeSpinner.getModel()).setCalendarField(calendarField);
}
Object value = (isNext) ? activeSpinner.getNextValue() : activeSpinner.getPreviousValue();
if (value != null) {
activeSpinner.setValue(value);
select(activeSpinner);
}
} catch (IllegalArgumentException | ParseException iae) {
UIManager.getLookAndFeel().provideErrorFeedback(activeSpinner);
}
}
}
/**
* If the spinner's editor is a DateEditor, this selects the field
* associated with the value that is being incremented.
*/
private void select(JSpinner spinnerComponent) {
JComponent editor = spinnerComponent.getEditor();
if (!(editor instanceof JSpinner.DateEditor)) return;
JSpinner.DateEditor dateEditor = (JSpinner.DateEditor)editor;
JFormattedTextField ftf = dateEditor.getTextField();
Format format = dateEditor.getFormat();
Object value;
if (format == null || (value = spinnerComponent.getValue()) == null) return;
SpinnerDateModel model = dateEditor.getModel();
DateFormat.Field field = DateFormat.Field.ofCalendarField(model.getCalendarField());
if (field == null) return;
try {
AttributedCharacterIterator iterator = format.formatToCharacterIterator(value);
if (!select(ftf, iterator, field) && field == DateFormat.Field.HOUR0) {
select(ftf, iterator, DateFormat.Field.HOUR1);
}
} catch (IllegalArgumentException iae) {}
}
/**
* Selects the passed in field, returning true if it is found,
* false otherwise.
*/
private boolean select(JFormattedTextField ftf, AttributedCharacterIterator iterator, DateFormat.Field field) {
int max = ftf.getDocument().getLength();
iterator.first();
do {
Map attrs = iterator.getAttributes();
if (attrs == null || !attrs.containsKey(field)) continue;
int start = iterator.getRunStart(field);
int end = iterator.getRunLimit(field);
if (start != -1 && end != -1 && start <= max && end <= max) {
ftf.select(start, end);
}
return true;
} while (iterator.next() != CharacterIterator.DONE);
return false;
}
/**
* Returns the calendarField under the start of the selection, or
* -1 if there is no valid calendar field under the selection (or
* the spinner isn't editing dates.
*/
private int getCalendarField(JSpinner spinnerComponent) {
JComponent editor = spinnerComponent.getEditor();
if (!(editor instanceof JSpinner.DateEditor)) return -1;
JSpinner.DateEditor dateEditor = (JSpinner.DateEditor)editor;
JFormattedTextField ftf = dateEditor.getTextField();
int start = ftf.getSelectionStart();
JFormattedTextField.AbstractFormatter formatter = ftf.getFormatter();
if (!(formatter instanceof InternationalFormatter)) return -1;
Format.Field[] fields = ((InternationalFormatter)formatter).getFields(start);
for (Field element : fields) {
if (!(element instanceof DateFormat.Field)) continue;
int calendarField;
if (element == DateFormat.Field.HOUR1) {
calendarField = Calendar.HOUR;
} else {
calendarField = ((DateFormat.Field)element).getCalendarField();
}
if (calendarField != -1) {
return calendarField;
}
}
return -1;
}
public void mousePressed(MouseEvent e) {
if (!SwingUtilities.isLeftMouseButton(e) || !e.getComponent().isEnabled()) {
return;
}
activeSpinner = eventToSpinner(e);
if (activeSpinner != null) {
activeSpinner.repaint();
autoRepeatTimer.start();
focusSpinnerIfNecessary();
}
}
public void mouseReleased(MouseEvent e) {
autoRepeatTimer.stop();
if (activeSpinner != null) {
activeSpinner.repaint();
activeSpinner = null;
}
}
public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
/**
* Requests focus on a child of the spinner if the spinner doesn't have focus.
*/
private void focusSpinnerIfNecessary() {
assert activeSpinner != null;
Component fo = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (!activeSpinner.isRequestFocusEnabled() || (fo != null && (SwingUtilities.isDescendingFrom(fo, activeSpinner)))) return;
Container root = activeSpinner;
if (!root.isFocusCycleRoot()) {
root = root.getFocusCycleRootAncestor();
}
if (root == null) return;
FocusTraversalPolicy ftp = root.getFocusTraversalPolicy();
Component child = ftp.getComponentAfter(root, activeSpinner);
if (child != null && SwingUtilities.isDescendingFrom(child, activeSpinner)) {
JavaSupport.requestFocusInWindowFromMouseEvent(child);
}
}
}
@SuppressWarnings("serial") // Superclass is not serializable across versions
class SpinPainter extends JComponent {
ButtonModel fTopModel;
ButtonModel fBottomModel;
public SpinPainter(AbstractButton top, AbstractButton bottom) {
if (top != null) {
fTopModel = top.getModel();
}
if (bottom != null) {
fBottomModel = bottom.getModel();
}
setUI(new SpinnerPainterUI());
}
// This method is invoked by reflection
public ComponentUI getUI() {
return ui;
}
public void paint(Graphics g) {
if (spinner.isOpaque()) {
g.setColor(spinner.getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
}
Rectangle bounds = getBounds();
AppearanceManager.ensureAppearance(spinner);
AquaUtils.configure(painter, spinner, bounds.width, bounds.height);
Configuration cg = getConfiguration();
painter.getPainter(cg).paint(g, 0, 0);
}
protected Configuration getConfiguration() {
Size size = AquaUtilControlSize.getUserSizeFrom(spinner);
State state;
boolean isPressedTop = false;
if (isEnabled()) {
if (fTopModel != null && fTopModel.isPressed()) {
state = State.PRESSED;
isPressedTop = true;
} else if (fBottomModel != null && fBottomModel.isPressed()) {
state = State.PRESSED;
} else {
state = State.ACTIVE;
}
} else {
state = State.DISABLED;
}
boolean isFocused = false;
return new SpinnerArrowsConfiguration(size, state, isFocused, isPressedTop);
}
public Dimension getPreferredSize() {
Size size = AquaUtilControlSize.getUserSizeFrom(spinner);
SpinnerArrowsLayoutConfiguration g = new SpinnerArrowsLayoutConfiguration(size);
LayoutInfo layoutInfo = painter.getLayoutInfo().getLayoutInfo(g);
int width = (int) layoutInfo.getFixedVisualWidth();
int height = (int) layoutInfo.getFixedVisualHeight();
if (width == 0) {
width = 14;
}
if (height == 0) {
height = 23;
}
return new Dimension(width, height);
}
}
class SpinnerPainterUI extends ComponentUI implements FocusRingOutlineProvider {
@Override
public @Nullable Shape getFocusRingOutline(@NotNull JComponent c) {
Size size = AquaUtilControlSize.getUserSizeFrom(spinner);
SpinnerArrowsLayoutConfiguration g = new SpinnerArrowsLayoutConfiguration(size);
Rectangle bounds = c.getBounds();
AppearanceManager.ensureAppearance(c);
AquaUtils.configure(painter, c, bounds.width, bounds.height);
return painter.getOutline(g);
}
@Override
public void installUI(JComponent c) {
super.installUI(c);
// On Yosemite, the spinner control is a single control containing two buttons. The spinner control is
// focusable in Full Keyboard Access mode. The individual buttons are not focusable.
OSXSystemProperties.register(c);
}
@Override
public void uninstallUI(JComponent c) {
OSXSystemProperties.unregister(c);
super.uninstallUI(c);
}
}
/**
* A simple layout manager for the editor and the next/previous buttons.
* See the AquaSpinnerUI javadoc for more information about exactly
* how the components are arranged.
*/
static class SpinnerLayout implements LayoutManager {
private Component nextButton = null;
private Component previousButton = null;
private Component editor = null;
private Component painter = null;
public void addLayoutComponent(String name, Component c) {
if ("Next".equals(name)) {
nextButton = c;
} else if ("Previous".equals(name)) {
previousButton = c;
} else if ("Editor".equals(name)) {
editor = c;
} else if ("Painter".equals(name)) {
painter = c;
}
}
public void removeLayoutComponent(Component c) {
if (c == nextButton) {
c = null;
} else if (c == previousButton) {
previousButton = null;
} else if (c == editor) {
editor = null;
} else if (c == painter) {
painter = null;
}
}
private Dimension preferredSize(Component c) {
return (c == null) ? new Dimension(0, 0) : c.getPreferredSize();
}
public Dimension preferredLayoutSize(Container parent) {
// Dimension nextD = preferredSize(nextButton);
// Dimension previousD = preferredSize(previousButton);
Dimension editorD = preferredSize(editor);
Dimension painterD = preferredSize(painter);
/* Force the editors height to be a multiple of 2
*/
editorD.height = ((editorD.height + 1) / 2) * 2;
Dimension size = new Dimension(editorD.width, Math.max(painterD.height, editorD.height));
size.width += painterD.width; //Math.max(nextD.width, previousD.width);
Insets insets = parent.getInsets();
size.width += insets.left + insets.right + SEPARATION;
size.height += insets.top + insets.bottom;
return size;
}
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
private void setBounds(Component c, int x, int y, int width, int height) {
if (c != null) {
c.setBounds(x, y, width, height);
}
}
public void layoutContainer(Container parent) {
Insets insets = parent.getInsets();
int availWidth = parent.getWidth() - (insets.left + insets.right);
int availHeight = parent.getHeight() - (insets.top + insets.bottom);
Dimension painterD = preferredSize(painter);
// Dimension nextD = preferredSize(nextButton);
// Dimension previousD = preferredSize(previousButton);
int nextHeight = availHeight / 2;
int previousHeight = availHeight - nextHeight;
int buttonsWidth = painterD.width; //Math.max(nextD.width, previousD.width);
int editorWidth = availWidth - buttonsWidth - SEPARATION;
/* Deal with the spinners componentOrientation property.
*/
int editorX, buttonsX;
if (parent.getComponentOrientation().isLeftToRight()) {
editorX = insets.left;
buttonsX = editorX + editorWidth + SEPARATION;
} else {
buttonsX = insets.left;
editorX = buttonsX + buttonsWidth + SEPARATION;
}
int previousY = insets.top + nextHeight;
int painterTop = previousY - (painterD.height / 2);
setBounds(editor, editorX, insets.top, editorWidth, availHeight);
setBounds(nextButton, buttonsX, insets.top, buttonsWidth, nextHeight);
setBounds(previousButton, buttonsX, previousY, buttonsWidth, previousHeight);
setBounds(painter, buttonsX, painterTop, buttonsWidth, painterD.height);
}
}
/**
* Detect JSpinner property changes we're interested in and delegate. Subclasses
* shouldn't need to replace the default propertyChangeListener (although they
* can by overriding createPropertyChangeListener) since all of the interesting
* property changes are delegated to protected methods.
*/
static class PropertyChangeHandler implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
JSpinner spinner = (JSpinner)(e.getSource());
SpinnerUI spinnerUI = spinner.getUI();
if (spinnerUI instanceof AquaSpinnerUI) {
AquaSpinnerUI ui = (AquaSpinnerUI)spinnerUI;
if ("editor".equals(propertyName)) {
JComponent oldEditor = (JComponent)e.getOldValue();
JComponent newEditor = (JComponent)e.getNewValue();
ui.replaceEditor(oldEditor, newEditor);
ui.updateEnabledState();
} else if ("enabled".equals(propertyName)) {
ui.updateEnabledState();
} else if (JComponent.TOOL_TIP_TEXT_KEY.equals(propertyName)) {
ui.updateToolTipTextForChildren(spinner);
} else if ("font".equals(propertyName)) {
JComponent editor = spinner.getEditor();
if (editor != null && editor instanceof JSpinner.DefaultEditor) {
JTextField tf =
((JSpinner.DefaultEditor) editor).getTextField();
if (tf != null) {
if (tf.getFont() instanceof UIResource) {
tf.setFont(spinner.getFont());
}
}
}
} else if (AquaUtilControlSize.CLIENT_PROPERTY_KEY.equals(propertyName)) {
JComponent editor = spinner.getEditor();
if (editor != null && editor instanceof JSpinner.DefaultEditor) {
JTextField tf =
((JSpinner.DefaultEditor) editor).getTextField();
if (tf != null) {
tf.putClientProperty(propertyName, e.getNewValue());
}
}
}
}
}
}
// Synchronizes the ToolTip text for the components within the spinner
// to be the same value as the spinner ToolTip text.
void updateToolTipTextForChildren(JComponent spinnerComponent) {
String toolTipText = spinnerComponent.getToolTipText();
Component[] children = spinnerComponent.getComponents();
for (Component element : children) {
if (element instanceof JSpinner.DefaultEditor) {
JTextField tf = ((JSpinner.DefaultEditor)element).getTextField();
if (tf != null) {
tf.setToolTipText(toolTipText);
}
} else if (element instanceof JComponent) {
((JComponent)element).setToolTipText(toolTipText);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy