TristateCheckBox

Ich war auf der Suche nach einer TristateCheckbox und hab iwie keine kurze Loesung gefunden.
Bei Apache musste ich das gesammte JMeter project hinzufuegen und die Quellcodes die ich auf Coderanch oder auf javaspecialists gefunden hab
brachten keine gute Checkbox zustande sodass diese nicht mehr dem Look and Feel entsprach.

Aus diesem Grund hab ich mir selbst etwas kleines geschrieben das ich nun mit euch teilen moechte.


import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;

/**
* Created by doctor on 11.02.14.
*/
public class TristateCheckBox extends JComponent implements ItemSelectable {
	public static enum State {SELECTED, DESELECTED, TRISATE}
	private final JCheckBox checkBox;
	private final MouseListener mouseListener;
	private final KeyListener keyListener;

	private String actionCommand = "TristateCheckBox";

	private State currentState = State.DESELECTED;
	private final Icon simpleIcon ;
	private final Icon triState = new TriStateIcon();
	private boolean supress = false;

	private List<ItemListener> itemListeners = new LinkedList<>();

	private List<ActionListener> actionListeners = new LinkedList<>();

	public TristateCheckBox() {
		this(null, null, false);
	}

	/**
	 * Creates an initially unselected check box with an icon.
	 *
	 * @param icon  the Icon image to display
	 */
	public TristateCheckBox(Icon icon) {
		this(null, icon, false);
	}

	/**
	 * Creates a check box with an icon and specifies whether
	 * or not it is initially selected.
	 *
	 * @param icon  the Icon image to display
	 * @param selected a boolean value indicating the initial selection
	 *        state. If <code>true</code> the check box is selected
	 */
	public TristateCheckBox(Icon icon, boolean selected) {
		this(null, icon, selected);
	}

	/**
	 * Creates an initially unselected  check box with text.
	 *
	 * @param text the text of the check box.
	 */
	public TristateCheckBox(String text) {
		this(text, null, false);
	}




	/**
	 * Creates a check box with text and specifies whether
	 * or not it is initially selected.
	 *
	 * @param text the text of the check box.
	 * @param selected a boolean value indicating the initial selection
	 *        state. If <code>true</code> the check box is selected
	 */
	public TristateCheckBox(String text, boolean selected) {
		this(text, null, selected);
	}

	/**
	 * Creates an initially unselected check box with
	 * the specified text and icon.
	 *
	 * @param text the text of the check box.
	 * @param icon  the Icon image to display
	 */
	public TristateCheckBox(String text, Icon icon) {
		this(text, icon, false);
	}

	/**
	 * Creates a check box with text and icon,
	 * and specifies whether or not it is initially selected.
	 *
	 * @param text the text of the check box.
	 * @param icon  the Icon image to display
	 * @param selected a boolean value indicating the initial selection
	 *        state. If <code>true</code> the check box is selected
	 */
	public TristateCheckBox(String text, Icon icon, boolean selected) {


		checkBox = new JCheckBox(text, icon, selected){

			@Override
			protected void processKeyEvent(KeyEvent e) {
				TristateCheckBox.this.processKeyEvent(e);
			}

			@Override
			protected void processMouseEvent(MouseEvent e) {
				TristateCheckBox.this.processMouseEvent(e);
			}

			@Override
			protected void processMouseMotionEvent(MouseEvent e) {
				TristateCheckBox.this.processMouseMotionEvent(e);
			}
		};
		simpleIcon = checkBox.getIcon();

		this.setLayout(new GridLayout(1,1));
		this.add(checkBox);
		this.mouseListener = new InnerMouseListener();
		this.keyListener = new InnerKeyListener();
		this.addMouseListener(mouseListener);
		this.addKeyListener(keyListener);
	}


	@Override
	public void removeMouseListener(MouseListener l) {
		if (l!= mouseListener){
			super.removeMouseListener(l);
		}
	}

	@Override
	public void removeKeyListener(KeyListener l) {
		if (l!= keyListener){
			super.removeKeyListener(l);
		}
	}

	public void setState(State newState) {
		if (newState == currentState){
			return;
		}
		State oldState = currentState;
		if (currentState == State.TRISATE ){
		   removeTristateSettings();
		}
		switch (newState) {
			case SELECTED:
				checkBox.setSelected(true);
				currentState = State.SELECTED;
				break;
			case TRISATE:
				setTristate();
				break;
			case DESELECTED:
				checkBox.setSelected(false);
				currentState= State.DESELECTED;
				break;
		}

		fireItemStateChanged();
		super.firePropertyChange("Selection",oldState,currentState);
	}

	private void fireItemStateChanged() {
		ItemEvent event = new TriStateEvent(this,ItemEvent.ITEM_STATE_CHANGED,this,ItemEvent.ITEM_STATE_CHANGED);
	   for (ItemListener itemListener:itemListeners){
		   itemListener.itemStateChanged(event);
	   }
	}

	private void fireActionPerformed(ActionEvent e){
		for (ActionListener listener:actionListeners){
			listener.actionPerformed(e);

		}
	}

	@Override
	public Object[] getSelectedObjects() {
		return new Object[]{currentState};
	}

	public void addActionListener(ActionListener listener){
		actionListeners.add(listener);
	}

	public void removeActionListener(ActionListener listener){
		actionListeners.remove(listener);
	}

	public ActionListener[] getActionListeners(){
		return actionListeners.toArray(new ActionListener[itemListeners.size()]);
	}

	public void addItemListener(ItemListener listener){
		itemListeners.add(listener);
	}

	public void removeItemListener(ItemListener listener){
		itemListeners.remove(listener);
	}

	public ItemListener[] getItemListeners(){
		return itemListeners.toArray(new ItemListener[itemListeners.size()]);
	}



	public static class TriStateEvent extends ItemEvent{

		public final int TRISTATE = 3;
		final private int stateChanged;

		/**
		 * Constructs an <code>ItemEvent</code> object.
		 * <p> This method throws an
		 * <code>IllegalArgumentException</code> if <code>source</code>
		 * is <code>null</code>.
		 *
		 * @param source      The <code>ItemSelectable</code> object
		 *                    that originated the event
		 * @param id          The integer that identifies the event type.
		 *                    For information on allowable values, see
		 *                    the class description for {@link java.awt.event.ItemEvent}
		 * @param item        An object -- the item affected by the event
		 * @param stateChange An integer that indicates whether the item was
		 *                    selected or deselected.
		 *                    For information on allowable values, see
		 *                    the class description for {@link java.awt.event.ItemEvent}
		 * @throws IllegalArgumentException if <code>source</code> is null
		 * @see #getItemSelectable()
		 * @see #getID()
		 * @see #getStateChange()
		 */
		public TriStateEvent(ItemSelectable source, int id, Object item, int stateChange) {
			super(source, id, item, ItemEvent.SELECTED);
			this.stateChanged = stateChange;
		}

		@Override
		public int getStateChange() {
			return stateChanged;
		}

		@Override
		public String paramString() {
			String typeStr;
			switch(id) {
				case ITEM_STATE_CHANGED:
					typeStr = "ITEM_STATE_CHANGED";
					break;
				default:
					typeStr = "unknown type";
			}

			String stateStr;
			switch(stateChanged) {
				case SELECTED:
					stateStr = "SELECTED";
					break;
				case DESELECTED:
					stateStr = "DESELECTED";
					break;
				case TRISTATE:
					stateStr = "TRISTATE";
				default:
					stateStr = "unknown type";
			}
			return typeStr + ",item="+getItem() + ",stateChange="+stateStr;
		}
	}

	public void setActionCommand(String newCommand){
		this.actionCommand= newCommand;
	}

	public String getActionCommand(){
		return actionCommand;
	}


	private class InnerKeyListener extends KeyAdapter {

		@Override
		public void keyReleased(KeyEvent e) {
			changeState();
			fireActionPerformed((new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
					getActionCommand(),
					EventQueue.getMostRecentEventTime(),
					0)));
		}
	}

	private  class InnerMouseListener extends MouseAdapter{


		@Override
		public void mouseReleased(MouseEvent e) {
			  changeState();
			fireActionPerformed((new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
					getActionCommand(),
					EventQueue.getMostRecentEventTime(),
					0)));
		}


	}
	private void changeState() {
		if (currentState == State.SELECTED){
			setState(State.DESELECTED);
		}else{
			setState(State.SELECTED);
		}
	}

	public State getState() {
		return currentState;
	}



	private void setTristate() {

		checkBox.setIcon(triState);
		currentState = State.TRISATE;
		checkBox.setSelected(false);

	}

	private void removeTristateSettings() {
		//checkBox.setSelectedIcon(selectedIcon);
		checkBox.setIcon(simpleIcon);
	}


	private class TriStateIcon implements Icon {

		private boolean  drawSelf =false;
		private Icon basicIcon = buildIcon();


		private Icon buildIcon (){
			  Icon ico = UIManager.getIcon("TristateCheckBox.icon");
			if (ico == null){
				ico = UIManager.getIcon("CheckBox.icon");
				drawSelf = true;
			}

			return ico;
		}

		@Override
		public void paintIcon(Component c, Graphics g, int x, int y) {
			g.setColor(Color.darkGray);
			basicIcon.paintIcon(c, g, x, y);
			if (drawSelf)
				g.fillRect(x + 3, y + 3, getIconWidth() - 6, getIconHeight() - 6);
		}

		@Override
		public int getIconWidth() {
			return basicIcon.getIconWidth();
		}

		@Override
		public int getIconHeight() {
			return basicIcon.getIconHeight();
		}
	}
}

EDIT+++++

Wuerde mich freuen wenn ein Mac OS X nutzer mir einen Screenshot von meinem Test Programm machen koennte, da ich dies dort noch nicht testen konnte.

	    try {
		    // Set System L&F
		    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
	    }
	    catch (UnsupportedLookAndFeelException e) {
		    // handle exception
	    }
	    catch (ClassNotFoundException e) {
		    // handle exception
	    }
	    catch (InstantiationException e) {
		    // handle exception
	    }
	    catch (IllegalAccessException e) {
		    // handle exception
	    }

        JFrame f = new JFrame();
	    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	    f.setSize(new Dimension(200,200));
	    final TristateCheckBox c1 = new TristateCheckBox("Test");
	    JButton b1 = new JButton("SELECT");
	    JButton b2 = new JButton("TRI");
	    JButton b3 = new JButton("DESELECT");

	    b1.addActionListener(new ActionListener() {
		    @Override
		    public void actionPerformed(ActionEvent e) {
			    c1.setState(TristateCheckBox.State.SELECTED);
		    }
	    });
	    b2.addActionListener(new ActionListener() {
		    @Override
		    public void actionPerformed(ActionEvent e) {
			    c1.setState(TristateCheckBox.State.TRISATE);
		    }
	    });
	    b3.addActionListener(new ActionListener() {
		    @Override
		    public void actionPerformed(ActionEvent e) {
			    c1.setState(TristateCheckBox.State.DESELECTED);
		    }
	    });
	     c1.setState(TristateCheckBox.State.TRISATE);

	    JPanel p = new JPanel();
	    p.add(c1);
	    p.add(b1);
	    p.add(b2);
	    p.add(b3);
	    f.setContentPane(p);
	    f.setVisible(true);

    }```