When changing tabs, JTabbedPane always focuses the first focusable Component inside the tab.
How can I change its behaviour so that it will either focus the last focused component or none at all?
Invoking requestFocus afterwards is not an option because JTabbedPane must not set the focus in the wrong field at all.
Take a look at: Remembering last focused component.
You need to keep track of which component has focus in each tab. Then when a tab is selected, you need to change focus to the appropriate component. Here is the code taken from the link above:
class TabbedPaneFocus extends JTabbedPane implements ChangeListener, PropertyChangeListener {
private Hashtable tabFocus;
public TabbedPaneFocus() {
tabFocus = new Hashtable();
addChangeListener(this);
KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(this);
}
/*
* (non-Javadoc)
*
* #see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
*/
#Override
public void propertyChange(PropertyChangeEvent e) {
if ("permanentFocusOwner".equals(e.getPropertyName())) {
final Object value = e.getNewValue();
if (value != null) {
tabFocus.put(getTitleAt(getSelectedIndex()), value);
}
}
}
/*
* (non-Javadoc)
*
* #see javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent)
*/
#Override
public void stateChanged(ChangeEvent e) {
Object value = tabFocus.get(getTitleAt(getSelectedIndex()));
if (value != null) {
((Component) value).requestFocusInWindow();
}
}
}
basically this works inside one Top-Level Container correctly
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
someComponent.grabFocus();
someComponent.requestFocus();//or inWindow
}
});
Related
I am writing a class (UIPromptComboBox) that extends JComboBox. The combobox is editable and for one application of the class it is implemented with a controlling ActionListener.
Currently, when the combobox is edited it fires the ActionListener which is good. However this ActionListener is also fired when I deselect the combobox and I cannot distinguish between the two events nor do I want it to fire when the combobox is deselected.
Implementing Class
private void addUIField() {
// Initialise and place combobox
this.myGuiTextField = new UIPromptComboBox();
myGuiTextField.setSize(COMBO_WIDTH, defaultHeight);
GuiUtils.positionControl(myPanel, myGuiTextField, myTop, PROMPT_X_LOC);
//Add action listener
myGuiTextField.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
if (evt.getActionCommand().equals("comboBoxEdited")) {
newUIcreated((UIPromptComboBox) evt.getSource());
}
}
private void newUIcreated(UIPromptComboBox alteredGuiTextField) {
try {
UIPrompt uip = alteredGuiTextField.getUIPrompt(((PowerPointTextItem) myPPTRef).getValue());
if (!simInfo.isInPrompts(uip)) {
simInfo.addUIPrompt(uip);
alteredGuiTextField.addNewUIPrompt(uip);
}
} catch (MissingPowerpointItem ex) {
Exceptions.printStackTrace(ex);
}
}
});
}
Class that extends JComboBox
public class UIPromptComboBox extends JComboBox {
public UIPromptComboBox(UIPrompt[] items) {
super(items);
this.setEditable(true);
}
public UIPromptComboBox() {
this.setEditable(true);
this.setEnabled(false);
}
/**
* returns either the selected UI prompt or a new prompt using the example
* text
*
* #param exampleText only used if new prompt is created
* #return UI prompt selected
*/
public UIPrompt getUIPrompt(String exampleText) {
UIPrompt uIPrompt = null;
Object returnedItem = this.getSelectedItem();
if (returnedItem instanceof UIPrompt) {
uIPrompt = (UIPrompt) returnedItem;
} else if (returnedItem instanceof String) {
uIPrompt = new UIPrompt((String) returnedItem, exampleText);
}
return uIPrompt;
}
public void addNewUIPrompt(UIPrompt newPrompt) {
ActionListener[] actionListerners = this.getActionListeners();
this.removeActionListener(this);
this.addItem(newPrompt);
this.setSelectedItem(newPrompt);
for (ActionListener al : actionListerners) {
this.addActionListener(al);
}
}
/**
* Used for displaying a report value sentence
* i.e. a string that is not associated with UI Prompts
* #param newText report value sentence
*/
public void setText(String newText) {
this.removeAllItems();
this.addItem(newText);
this.setSelectedItem(newText);
}
/**
* For when the UI prompts can be added on construction
*
* #param currentUIs list of UI promts
*/
public void addItems(UIPrompt[] currentUIs) {
this.removeAllItems();
DefaultComboBoxModel boxModel = new DefaultComboBoxModel(currentUIs);
this.setModel(boxModel);
}
}
The multiple firing due to losing focus is causing multiple objects to be created and added to the list. I think I may have implemented the ActionListener incorrectly. Thank you for your help
as you stated you only want the event to fire if the user presses enter. a better way to implement that, would be using an keylistener instead of an action listener.
myGuiTextField.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
newUIcreated((UIPromptComboBox) evt.getSource());
}
}
private void newUIcreated(UIPromptComboBox alteredGuiTextField) {
try {
UIPrompt uip = alteredGuiTextField.getUIPrompt(((PowerPointTextItem) myPPTRef).getValue());
if (!simInfo.isInPrompts(uip)) {
simInfo.addUIPrompt(uip);
alteredGuiTextField.addNewUIPrompt(uip);
}
} catch (MissingPowerpointItem ex) {
Exceptions.printStackTrace(ex);
}
}
});
this should now only fire your event newUIcreated, once the users presses enter and at no other time. replace your action listener with this
I have now finally found the issue.
The displaying of the UIPrompt included the addition of a string that sometimes contained a new line character.
The action of clicking another field was triggering the render of the UIPrompt however when this contained a newline character it was triggering the ActionListener again. This what the reason for the repeated action of comboBoxEdited.
class RuleComboBox extends JComboBox {
public RuleComboBox() {
super();
this.setModel(new javax.swing.DefaultComboBoxModel(new String[]{"abc", "defg"}));
((JLabel) this.getRenderer()).setHorizontalAlignment(SwingConstants.CENTER);
}
}
The getRenderer() line aligns the text to centre.
When I useruleComboBox1.setEnabled(false) and ruleComboBox1.setEditable(true), the text aligns back to the left which I don't want. How can I stop this?
I should explain that I'm using setEditable(true) to keep the appearance of the text within the ComboBox when I disable it.
The editor for a JComboBox must implement the ComboBoxEditor interface. The default implementation extends from BasicComboBoxEditor which returns a JTextField as the editor. A JTextField doesn't support the concept of displaying text centered.
So you can implement your own ComboBoxEditor. I would suggest you can just use the BasicComboBoxEditor and change the code to create a JTextPane instead of a JTextField. Then when you create the text pane you can use:
SimpleAttributeSet center = new SimpleAttributeSet();
StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER);
StyledDocument doc = textPane.getStyledDocument();
doc.setParagraphAttributes(0, doc.getLength(), center, false);
which will cause the code to be centered.
Note: this will not be a straight forward conversion. A JTextField invokes an ActionListener when the Enter key is pressed. A JTextPane doesn't support this functionality (the default is to insert a new line) so you will need to replicate this functionality for the JTextPane. That is you will need to use Key Bindings to handle the Enter key. So you will need to wrap the ActionListener in a custom Action and then bind the Enter key to the text pane.
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.border.Border;
import java.awt.Component;
import java.awt.event.*;
import java.lang.reflect.Method;
//import sun.reflect.misc.MethodUtil;
/**
* A custom editor for editable combo boxes. The editor is implemented as a JTextPane.
*
*/
public class TextPaneComboBoxEditor implements ComboBoxEditor {
protected JTextPane editor;
private Object oldValue;
public TextPaneComboBoxEditor() {
editor = createEditorComponent();
}
public Component getEditorComponent() {
return editor;
}
/**
* Creates the internal editor component. Override this to provide
* a custom implementation.
*
* #return a new editor component
* #since 1.6
*/
protected JTextPane createEditorComponent() {
JTextPane editor = new BorderlessTextPane("",9);
editor.setBorder(null);
SimpleAttributeSet center = new SimpleAttributeSet();
StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER);
StyledDocument doc = editor.getStyledDocument();
doc.setParagraphAttributes(0, doc.getLength(), center, false);
return editor;
}
/**
* Sets the item that should be edited.
*
* #param anObject the displayed value of the editor
*/
public void setItem(Object anObject) {
String text;
if ( anObject != null ) {
text = anObject.toString();
if (text == null) {
text = "";
}
oldValue = anObject;
} else {
text = "";
}
// workaround for 4530952
if (! text.equals(editor.getText())) {
editor.setText(text);
}
}
public Object getItem() {
Object newValue = editor.getText();
// This code only works for Strings. The default implementation would
// use reflection to create Object of whatever class was stored in the
// ComboBoxModel. You will need to fix the reflection code if you want
// to support other types of data in the model
/*
if (oldValue != null && !(oldValue instanceof String)) {
// The original value is not a string. Should return the value in it's
// original type.
if (newValue.equals(oldValue.toString())) {
return oldValue;
} else {
// Must take the value from the editor and get the value and cast it to the new type.
Class<?> cls = oldValue.getClass();
try {
Method method = MethodUtil.getMethod(cls, "valueOf", new Class[]{String.class});
newValue = MethodUtil.invoke(method, oldValue, new Object[] { editor.getText()});
} catch (Exception ex) {
// Fail silently and return the newValue (a String object)
}
}
}
*/
return newValue;
}
public void selectAll() {
editor.selectAll();
editor.requestFocus();
}
public void addActionListener(ActionListener l) {
// editor.addActionListener(l);
Action enter = new WrappedActionListener(l);
editor.getActionMap().put("insert-break", enter);
}
public void removeActionListener(ActionListener l) {
// editor.removeActionListener(l);
}
static class BorderlessTextPane extends JTextPane {
public BorderlessTextPane(String value,int n) {
// super(value,n);
setText(value);
}
// workaround for 4530952
public void setText(String s) {
if (getText().equals(s)) {
return;
}
super.setText(s);
}
public void setBorder(Border b) {
if (!(b instanceof UIResource)) {
super.setBorder(b);
}
}
}
/**
* A subclass of TextPaneComboBoxEditor that implements UIResource.
* TextPaneComboBoxEditor doesn't implement UIResource
* directly so that applications can safely override the
* cellRenderer property with TextPaneListCellRenderer subclasses.
* <p>
* <strong>Warning:</strong>
* Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running
* the same version of Swing. As of 1.4, support for long term storage
* of all JavaBeans™
* has been added to the <code>java.beans</code> package.
* Please see {#link java.beans.XMLEncoder}.
*/
public static class UIResource extends TextPaneComboBoxEditor
implements javax.swing.plaf.UIResource {
}
static class WrappedActionListener extends AbstractAction
{
private ActionListener listener;
public WrappedActionListener(ActionListener listener)
{
this.listener = listener;
}
#Override
public void actionPerformed(ActionEvent e)
{
listener.actionPerformed(e);
}
}
}
All you need in your current code is:
comboBox.setEditor( new TextPaneComboBoxEditor() );
If I create a new shell using the following code:
shell = new Shell( Display.getDefault(), SWT.RESIZE);
Then this gives me a shell without a title bar or minimize / maximize buttons, which is what I want. I'm able to resize this window to any size, which works great. But the problem is, the window is fixed in its place, and I cannot move it by dragging it around.
If I add either SWT.CASCADE or SWT.CLOSE, this gives me the title bar and close button, which I don't want, but moreover, it puts a limit on how small the window can be resized, i.e I can't resize it horizontally past a certain limit.
How can I make the window moveable without the close button / title bar? If there's no native way in SWT to do it, can I do it by listening for a mouse drag event and manually setting the location of the shell? If so, how would I get the mouse coordinates from the movement of the mouse?
Help would be appreciated. Thanks!
You need use own listeners. Below code should help:-
public class Demo {
static Boolean blnMouseDown=false;
static int xPos=0;
static int yPos=0;
public static void main(final String[] args) {
Display display=new Display();
final Shell shell = new Shell( Display.getDefault(), SWT.RESIZE);
shell.open();
shell.addMouseListener(new MouseListener() {
#Override
public void mouseUp(MouseEvent arg0) {
// TODO Auto-generated method stub
blnMouseDown=false;
}
#Override
public void mouseDown(MouseEvent e) {
// TODO Auto-generated method stub
blnMouseDown=true;
xPos=e.x;
yPos=e.y;
}
#Override
public void mouseDoubleClick(MouseEvent arg0) {
// TODO Auto-generated method stub
}
});
shell.addMouseMoveListener(new MouseMoveListener() {
#Override
public void mouseMove(MouseEvent e) {
// TODO Auto-generated method stub
if(blnMouseDown){
shell.setLocation(shell.getLocation().x+(e.x-xPos),shell.getLocation().y+(e.y-yPos));
}
}
});
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.close();
}
}
This is my implementation:
/**
* Class to allow user to move a shell without a title.
*
* #author Laurent Muller
* #version 1.0
*/
public class MoveShellListener implements Listener {
/*
* the parent shell
*/
private final Shell parent;
/*
* the mouse down location
*/
private Point ptMouseDown;
/**
* Creates a new instance of this class.
*
* #param parent
* the shell to handle.
*/
public MoveShellListener(final Shell parent) {
if (parent == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
if (parent.isDisposed()) {
SWT.error(SWT.ERROR_WIDGET_DISPOSED);
}
// copy and add listener
this.parent = parent;
addControl(parent);
}
/**
* Adds the given control to the list of listened controls. If the given
* control is an instance of {#link Composite}, the children controls are
* also added.
*
* #param control
* the control to add.
*/
public void addControl(final Control control) {
// check control
if (isDisposed(control) || control.getShell() != parent) {
return;
}
// add listeners
control.addListener(SWT.MouseDown, this);
control.addListener(SWT.MouseUp, this);
control.addListener(SWT.MouseMove, this);
// children
if (control instanceof Composite) {
final Control[] children = ((Composite) control).getChildren();
for (final Control child : children) {
addControl(child);
}
}
}
/**
* Adds the given controls to the list of listened controls. If one of the
* given controls is an instance of {#link Composite}, the children controls
* are also added.
*
* #param controls
* the controls to add.
*/
public void addControls(final Control... controls) {
if (controls != null) {
for (final Control control : controls) {
addControl(control);
}
}
}
/**
* {#inheritDoc}
*/
#Override
public void handleEvent(final Event e) {
switch (e.type) {
case SWT.MouseDown:
onMouseDown(e);
break;
case SWT.MouseUp:
onMouseUp(e);
break;
case SWT.MouseMove:
onMouseMove(e);
break;
}
}
/**
* Removes the given control to the list of listened controls. If the given
* control is an instance of {#link Composite}, the children controls are
* also removed.
*
* #param control
* the control to remove.
*/
public void removeControl(final Control control) {
// check control
if (control == parent || isDisposed(control)
|| control.getShell() != parent) {
return;
}
// remove listeners
control.removeListener(SWT.MouseDown, this);
control.removeListener(SWT.MouseUp, this);
control.removeListener(SWT.MouseMove, this);
// children
if (control instanceof Composite) {
final Control[] children = ((Composite) control).getChildren();
for (final Control child : children) {
removeControl(child);
}
}
}
/**
* Removes the given controls to the list of listened controls. If one of
* the given controls is an instance of {#link Composite}, the children
* controls are also removed.
*
* #param controls
* the controls to remove.
*/
public void removeControls(final Control... controls) {
if (controls != null) {
for (final Control control : controls) {
removeControl(control);
}
}
}
/**
* Checks if the given control is null or disposed.
*
* #param control
* the control to verify.
* #return true if the control is null or
* disposed.
*/
private boolean isDisposed(final Control control) {
return control == null || control.isDisposed();
}
/**
* Handles the mouse down event.
*
* #param e
* the event data.
*/
private void onMouseDown(final Event e) {
if (e.button == 1) {
ptMouseDown = new Point(e.x, e.y);
}
}
/**
* Handles the mouse move event.
*
* #param e
* the event data.
*/
private void onMouseMove(final Event e) {
if (ptMouseDown != null) {
final Point location = parent.getLocation();
location.x += e.x - ptMouseDown.x;
location.y += e.y - ptMouseDown.y;
parent.setLocation(location);
}
}
/**
* Handles the mouse up event.
*
* #param e
* the event data.
*/
private void onMouseUp(final Event e) {
ptMouseDown = null;
}
}
I have a swing component that has several sub components. What I want to do change some label if the mouse is over any of those components, and then change it to something else if the mouse moves off all of the components. I'm trying to find a more efficient way to do this.
Currently I have mouse listeners over all of the child components that look something like:
class AMouseListener extends MouseAdapter {
private boolean mouseOver;
mouseEntered(MouseEvent e) { mouseOver = true; updateLabel(); }
mouseExited(MouseEvent e) { mouseOver = false; updateLabel(); }
void updateLabel() {
String text = "not-over-any-components";
// listeners are each of the listeners added to the child components
for ( AMouseListener listener :listeners ) {
if ( listener.mouseOver ) {
text = "over-a-component";
break;
}
}
}
}
This works, but I feel like there should be a better way to handle this by only handling mouseEntered and mouseExited events on the parent container, but since the child components intercept these events, I'm not sure how to go about doing this (I don't necessarily have control over the child components so I Can't forward the mouse events to the parent event if I wanted to).
For example
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
public class TestMouseListener {
public static void main(String[] args) {
final JComboBox combo = new JComboBox();
combo.setEditable(true);
for (int i = 0; i < 10; i++) {
combo.addItem(i);
}
final JLabel tip = new JLabel();
tip.setPreferredSize(new Dimension(300, 20));
JPanel panel = new JPanel();
panel.add(combo);
panel.add(tip);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(panel);
frame.pack();
frame.setVisible(true);
panel.addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
tip.setText("Outside combobox");
}
#Override
public void mouseExited(MouseEvent e) {
Component c = SwingUtilities.getDeepestComponentAt(
e.getComponent(), e.getX(), e.getY());
// doesn't work if you move your mouse into the combobox popup
tip.setText(c != null && SwingUtilities.isDescendingFrom(
c, combo) ? "Inside combo box" : "Outside combobox");
}
});
}
private TestMouseListener() {
}
}
Check out the docs and examples for the "glass pane".
This should give you what you need: The Glass Pane
I know this is very old, but here's a simple solution with which you can create a mouse listener for a component and all components inside it's bounds (without adding the listener to all components individually):
/**
* Creates an {#link AWTEventListener} that will call the given listener if
* the {#link MouseEvent} occurred inside the given component, one of its
* children or the children's children etc. (recursive).
*
* #param component
* the component the {#link MouseEvent} has to occur inside
* #param listener
* the listener to be called if that is the case
*/
public static void addRecursiveMouseListener(final Component component, final MouseListener listener) {
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
if(event instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) event;
if(mouseEvent.getComponent().isShowing() && component.isShowing()){
if (containsScreenLocation(component, mouseEvent.getLocationOnScreen())) {
if(event.getID() == MouseEvent.MOUSE_PRESSED) {
listener.mousePressed(mouseEvent);
}
if(event.getID() == MouseEvent.MOUSE_RELEASED) {
listener.mouseReleased(mouseEvent);
}
if(event.getID() == MouseEvent.MOUSE_ENTERED) {
listener.mouseEntered(mouseEvent);
}
if(event.getID() == MouseEvent.MOUSE_EXITED) {
listener.mouseExited(mouseEvent);
}
if(event.getID() == MouseEvent.MOUSE_CLICKED){
listener.mouseClicked(mouseEvent);
}
}
}
}
}
}, AWTEvent.MOUSE_EVENT_MASK);
}
/**
* Checks if the given location (relative to the screen) is inside the given component
* #param component the component to check with
* #param screenLocation the location, relative to the screen
* #return true if it is inside the component, false otherwise
*/
public static boolean containsScreenLocation(Component component, Point screenLocation){
Point compLocation = component.getLocationOnScreen();
Dimension compSize = component.getSize();
int relativeX = screenLocation.x - compLocation.x;
int relativeY = screenLocation.y - compLocation.y;
return (relativeX >= 0 && relativeX < compSize.width && relativeY >= 0 && relativeY < compSize.height);
}
Note: Once the mouse exits the root component of this listener the mouseExited(mouseEvent) will probably not fire, however you can just add the mouse listener to the root component itself and it should fire.
mouseExited(mouseEvent) is unreliable in general though.
You could initiate a single instance of the listener and add that instance to each component.
Like this:
AMouseListener aMouseListener=new AMouseListener();
for each(Component c:components) {
caddMouseListener(aMouseListener);
}
In windows it is possible to show a grayed out JCheckbox, to show that the collection of data which it represents not all items have the same value.
Is this even possible with a JCheckBox?
How do i go about this?
(Hoping there's a way to not override it)
Thanks
JIDE Common Layer has a TristateCheckBox.
It's possible with some of work.
I have this code from some years ago. Is based in some examples I found in internet, but I cannot find any reference to the original creator, so I apologize
import javax.swing.*;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ActionMapUIResource;
import java.awt.event.*;
/**
* Maintenance tip - There were some tricks to getting this code
* working:
*
* 1. You have to overwite addMouseListener() to do nothing
* 2. You have to add a mouse event on mousePressed by calling
* super.addMouseListener()
* 3. You have to replace the UIActionMap for the keyboard event
* "pressed" with your own one.
* 4. You have to remove the UIActionMap for the keyboard event
* "released".
* 5. You have to grab focus when the next state is entered,
* otherwise clicking on the component won't get the focus.
* 6. You have to make a TristateDecorator as a button model that
* wraps the original button model and does state management.
*/
public class TristateCheckBox extends JCheckBox {
/** This is a type-safe enumerated type */
public static class State { private State() { } }
public static final State NOT_SELECTED = new State();
public static final State SELECTED = new State();
public static final State DONT_CARE = new State();
private final TristateDecorator model;
public TristateCheckBox(String text, Icon icon, State initial){
super(text, icon);
// Add a listener for when the mouse is pressed
super.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
grabFocus();
model.nextState();
}
});
// Reset the keyboard action map
ActionMap map = new ActionMapUIResource();
map.put("pressed", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
grabFocus();
model.nextState();
}
});
map.put("released", null);
SwingUtilities.replaceUIActionMap(this, map);
// set the model to the adapted model
model = new TristateDecorator(getModel());
setModel(model);
setState(initial);
}
public TristateCheckBox(String text, State initial) {
this(text, null, initial);
}
public TristateCheckBox(String text) {
this(text, DONT_CARE);
}
public TristateCheckBox() {
this(null);
}
/** No one may add mouse listeners, not even Swing! */
public void addMouseListener(MouseListener l) { }
/**
* Set the new state to either SELECTED, NOT_SELECTED or
* DONT_CARE. If state == null, it is treated as DONT_CARE.
*/
public void setState(State state) { model.setState(state); }
/** Return the current state, which is determined by the
* selection status of the model. */
public State getState() { return model.getState(); }
public void setSelected(boolean b) {
if (b) {
setState(SELECTED);
} else {
setState(NOT_SELECTED);
}
}
/**
* Exactly which Design Pattern is this? Is it an Adapter,
* a Proxy or a Decorator? In this case, my vote lies with the
* Decorator, because we are extending functionality and
* "decorating" the original model with a more powerful model.
*/
private class TristateDecorator implements ButtonModel {
private final ButtonModel other;
private TristateDecorator(ButtonModel other) {
this.other = other;
}
private void setState(State state) {
if (state == NOT_SELECTED) {
other.setArmed(false);
setPressed(false);
setSelected(false);
} else if (state == SELECTED) {
other.setArmed(false);
setPressed(false);
setSelected(true);
} else { // either "null" or DONT_CARE
other.setArmed(true);
setPressed(true);
setSelected(true);
}
}
/**
* The current state is embedded in the selection / armed
* state of the model.
*
* We return the SELECTED state when the checkbox is selected
* but not armed, DONT_CARE state when the checkbox is
* selected and armed (grey) and NOT_SELECTED when the
* checkbox is deselected.
*/
private State getState() {
if (isSelected() && !isArmed()) {
// normal black tick
return SELECTED;
} else if (isSelected() && isArmed()) {
// don't care grey tick
return DONT_CARE;
} else {
// normal deselected
return NOT_SELECTED;
}
}
/** We rotate between NOT_SELECTED, SELECTED and DONT_CARE.*/
private void nextState() {
State current = getState();
if (current == NOT_SELECTED) {
setState(SELECTED);
} else if (current == SELECTED) {
setState(DONT_CARE);
} else if (current == DONT_CARE) {
setState(NOT_SELECTED);
}
}
/** Filter: No one may change the armed status except us. */
public void setArmed(boolean b) {
}
/** We disable focusing on the component when it is not
* enabled. */
public void setEnabled(boolean b) {
setFocusable(b);
other.setEnabled(b);
}
/** All these methods simply delegate to the "other" model
* that is being decorated. */
public boolean isArmed() { return other.isArmed(); }
public boolean isSelected() { return other.isSelected(); }
public boolean isEnabled() { return other.isEnabled(); }
public boolean isPressed() { return other.isPressed(); }
public boolean isRollover() { return other.isRollover(); }
public void setSelected(boolean b) { other.setSelected(b); }
public void setPressed(boolean b) { other.setPressed(b); }
public void setRollover(boolean b) { other.setRollover(b); }
public void setMnemonic(int key) { other.setMnemonic(key); }
public int getMnemonic() { return other.getMnemonic(); }
public void setActionCommand(String s) {
other.setActionCommand(s);
}
public String getActionCommand() {
return other.getActionCommand();
}
public void setGroup(ButtonGroup group) {
other.setGroup(group);
}
public void addActionListener(ActionListener l) {
other.addActionListener(l);
}
public void removeActionListener(ActionListener l) {
other.removeActionListener(l);
}
public void addItemListener(ItemListener l) {
other.addItemListener(l);
}
public void removeItemListener(ItemListener l) {
other.removeItemListener(l);
}
public void addChangeListener(ChangeListener l) {
other.addChangeListener(l);
}
public void removeChangeListener(ChangeListener l) {
other.removeChangeListener(l);
}
public Object[] getSelectedObjects() {
return other.getSelectedObjects();
}
}
}
My colleague who this question came from thought of this;
Create a dummy JCheckBox which is disabled and selected. set the same size as the real one.
Create an Icon which' paint method actually paints the dummy JCheckbox.
Set the original JCheckBox' Icon to the one painting the dummy.
Remove the icon as soon as the JCheckBox is clicked.
++ No overridden JCheckBox
-- not a real tri-state Combo
I think he's satisfied.
Thanks for the help