/*
* Copyright 2004-2014 SmartBear Software
*
* Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent
* versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the Licence for the specific language governing permissions and limitations
* under the Licence.
*//**
* MySwing: Advanced Swing Utilites
* Copyright (C) 2005 Santhosh Kumar T
* <p/>
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* <p/>
* This library 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
* Lesser General Public License for more details.
*/
package skt.swing.tree.check;
import javax.swing.*;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ActionMapUIResource;
import java.awt.*;
import java.awt.event.*;
/**
* Maintenance tip - There were some tricks to getting this code
* working:
* <p/>
* 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.
* <p/>
* modifed version of :
* http://www.javaspecialists.co.za/archive/Issue082.html
*
* @author Dr. Heinz M. Kabutz
* @author Santhosh Kumar T
* @email santhosh@in.fiorano.com
*/
public class TristateCheckBox extends JCheckBox {
private final TristateDecorator model;
public TristateCheckBox(String text, Icon icon, Boolean 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() { //NOI18N
public void actionPerformed(ActionEvent e) {
grabFocus();
model.nextState();
}
});
map.put("released", null); //NOI18N
SwingUtilities.replaceUIActionMap(this, map);
// set the model to the adapted model
model = new TristateDecorator(getModel());
setModel(model);
setState(initial);
}
public TristateCheckBox(String text, Boolean initial) {
this(text, null, initial);
}
public TristateCheckBox(String text) {
this(text, null);
}
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(Boolean state) {
model.setState(state);
}
/**
* Return the current state, which is determined by the
* selection status of the model.
*/
public Boolean getState() {
return model.getState();
}
/**
* 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(Boolean state) {
if (state == Boolean.FALSE) {
other.setArmed(false);
setPressed(false);
setSelected(false);
} else if (state == Boolean.TRUE) {
other.setArmed(false);
setPressed(false);
setSelected(true);
} else {
other.setArmed(true);
setPressed(true);
setSelected(true);
}
}
/**
* The current state is embedded in the selection / armed
* state of the model.
* <p/>
* 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 Boolean getState() {
if (isSelected() && !isArmed()) {
// normal black tick
return Boolean.TRUE;
} else if (isSelected() && isArmed()) {
// don't care grey tick
return null;
} else {
// normal deselected
return Boolean.FALSE;
}
}
/**
* We rotate between NOT_SELECTED, SELECTED and DONT_CARE.
*/
private void nextState() {
Boolean current = getState();
if (current == Boolean.FALSE) {
setState(Boolean.TRUE);
} else if (current == Boolean.TRUE) {
setState(null);
} else if (current == null) {
setState(Boolean.FALSE);
}
}
/**
* Filter: No one may change the armed status except us.
*/
public void setArmed(boolean b) {
}
public boolean isFocusTraversable() {
return isEnabled();
}
/**
* 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();
}
}
public static void main(String args[]) throws Exception {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
JFrame frame = new JFrame("TristateCheckBoxTest"); //NOI18N
frame.getContentPane().setLayout(new GridLayout(0, 1, 5, 5));
final TristateCheckBox swingBox = new TristateCheckBox(
"Testing the tristate checkbox"); //NOI18N
swingBox.setMnemonic('T');
frame.getContentPane().add(swingBox);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}