I'd like to paint some additional information in the text field used as the default editor component of a JComboBox
using a JLayer
. To do this, I'd need to set the layer as the editor of the combo box through JComboBox.setEditor(ComboBoxEditor)
but that does not seem to be possible, since JLayer
is final and therefore cannot implement ComboBoxEditor
interface.
Is there a way to decorate a JComboBox
editor component with a JLayer
?
P.S.: The information I'd like to paint are cursor-like lines at certain text offsets within the text field, which is trivial for a JTextField
or a JTextArea
but not for an editable JComboBox
(its editor).
Edit:
This is the closest I've come to, after reading @camickr's answer, but not satisfied with it. The relevant part is extending BasicComboBoxEditor
.
import java.awt.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.plaf.LayerUI;
import javax.swing.plaf.basic.BasicComboBoxEditor;
import javax.swing.text.*;
public class GenericDecorateWithJLayer extends JFrame {
private static final String SAMPLE_TEXT = "Hello, world!";
private final Map<JComponent, List<Integer>> componentToPositions;
public GenericDecorateWithJLayer() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new GridBagLayout());
componentToPositions = new HashMap<JComponent, List<Integer>>();
GridBagConstraints gbc;
JLabel label1 = new JLabel("label1:");
gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
add(label1, gbc);
JTextField textfield1 = new JTextField(20);
textfield1.setText(SAMPLE_TEXT);
componentToPositions.put(textfield1, Arrays.asList(new Integer[]{5}));
gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 0;
add(textfield1, gbc);
JLabel label2 = new JLabel("label2:");
gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 1;
add(label2, gbc);
JTextField textfield2 = new JTextField(20);
textfield2.setText(SAMPLE_TEXT);
componentToPositions.put(textfield2, Arrays.asList(new Integer[]{6}));
gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 1;
add(textfield2, gbc);
JLabel label3 = new JLabel("label3:");
gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 2;
add(label3, gbc);
JTextArea textarea1 = new JTextArea(5, 20);
textarea1.setText(SAMPLE_TEXT);
componentToPositions.put(textarea1, Arrays.asList(new Integer[]{7}));
gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 2;
JScrollPane scroll1 = new JScrollPane(textarea1);
add(scroll1, gbc);
JLabel label4 = new JLabel("label4:");
gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 3;
add(label4, gbc);
JComboBox combobox1 = new JComboBox(new Object[]{SAMPLE_TEXT, "one", "two", "three"});
combobox1.setEditable(true);
combobox1.setSelectedItem(SAMPLE_TEXT);
componentToPositions.put(combobox1, Arrays.asList(new Integer[]{8}));
gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 3;
gbc.fill = GridBagConstraints.HORIZONTAL;
add(combobox1, gbc);
pack();
setLocationRelativeTo(null);
replaceWithJLayer(textfield1);
replaceWithJLayer(textfield2);
replaceWithJLayer(textarea1);
replaceWithJLayer(combobox1);
}
/**
* Intended to decorate legacy components.
*
* @param component
*/
private void replaceWithJLayer(JComponent component) {
Container parent = component.getParent();
if (component instanceof JComboBox) {
JComboBox cbb = (JComboBox) component;
cbb.setEditor(new MyComboBoxEditor(componentToPositions.get(cbb)));
} else if (parent.getLayout() instanceof GridBagLayout) {
GridBagLayout layout = (GridBagLayout) parent.getLayout();
for (int i = 0; i < parent.getComponentCount(); i++) {
Component candidate = parent.getComponent(i);
if (candidate == component) {
GridBagConstraints gbc = layout.getConstraints(component);
parent.remove(i);
JLayer<JComponent> layer = new JLayer<JComponent>(
component,
new MyLayerUI(
component,
componentToPositions.get(component)));
parent.add(layer, gbc, i);
break;
}
}
} else if (parent instanceof JViewport) {
JViewport viewport = (JViewport) parent;
JLayer<JComponent> layer = new JLayer<JComponent>(
component,
new MyLayerUI(
component, componentToPositions.get(component)));
viewport.setView(layer);
}
}
public static void main(String[] args)
throws ClassNotFoundException, InstantiationException,
IllegalAccessException, UnsupportedLookAndFeelException {
for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
}
System.out.println(info.getName());
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new GenericDecorateWithJLayer().setVisible(true);
}
});
}
private static class MyLayerUI extends LayerUI<JComponent> {
private final JComponent component;
private final List<Integer> positions;
public MyLayerUI(JComponent component, List<Integer> positions) {
this.component = component;
this.positions = positions;
}
@Override
public void paint(Graphics g, JComponent c) {
// paint the layer as is
super.paint(g, c);
// fill it with the translucent green
g.setColor(new Color(0, 128, 0, 128));
// paint positions
JTextComponent textComponent = (JTextComponent) component;
for (Integer position : positions) {
try {
Rectangle rect = textComponent.modelToView(position);
g.fillRect(rect.x, rect.y, rect.width, rect.height);
} catch (BadLocationException ex) {
// no-op
}
}
}
}
private static class MyComboBoxEditor extends BasicComboBoxEditor {
private final JLayer<JComponent> layer;
public MyComboBoxEditor(List<Integer> positions) {
super();
layer = new JLayer<JComponent>(editor, new MyLayerUI(editor, positions));
}
@Override
public Component getEditorComponent() {
return layer;
}
}
}