/*******************************************************************************
* Copyright (c) 2014, H. Marlovits and Elexis
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* H. Marlovits - initial implementation
*******************************************************************************/
package ch.elexis.core.ui.util;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import ch.elexis.core.constants.StringConstants;
import ch.elexis.data.PersistentObject.TristateBoolean;
/*
* how to set true/false/undef for checkbox composites:<br>
* true/checked:. . selection = true<br>
* . . . . . . . . . grayed = false<br>
* false/unchecked: selection = false<br>
* . . . . . . . . . grayed = false<br>
* undef: . . . . . selection = true<br>
* . . . . . . . . . grayed = true<br>
*/
/**
* A tristate checkbox. The states cycle through "filled with a square"/undefined/"partly selected",
* checked/true and unchecked/false.<br>
* The starting state is undef/filled with a square<br>
* To set the tristate selection state of the tristate checkbox use {@link #getTristate()}.<br>
* To set the tristate selection state of the tristate checkbox use.<br>
* {@link #setTristate(TristateBoolean)}.<br>
* To set the cycling order use {@link #setCyclingOrder(boolean)}. Default is false after undef.
*
* @author H. Marlovits
* @since 3.0.0
*/
public class TristateCheckbox extends Button {
private static final TristateBoolean START_STATE = TristateBoolean.UNDEF;
private static final boolean START_FALSEFIRST = true;
protected boolean falseFirst = START_FALSEFIRST;
/**
* Constructs a new instance of a checkbox-style button with tristate behaviour. False state
* follows after undefined.
*
* @param parent
* a composite control which will be the parent of the new instance (cannot be null)
* @param style
* the style of control to construct. You do not need to set SWT.CHECK which is
* automatically set.
* @author H. Marlovits
*/
public TristateCheckbox(Composite parent, int style){
super(parent, (style | SWT.CHECK));
addSelectionListener(new TristateSelectionListener(false));
setTristate(START_STATE);
setCyclingOrder(true);
}
/**
* Constructs a new instance of a checkbox-style button with tristate behaviour.
*
* @param parent
* a composite control which will be the parent of the new instance (cannot be null)
* @param style
* the style of control to construct. You do not need to set SWT.CHECK which is
* automatically set.
* @param falseFirst
* set how the states are cycling through. true if unchecked follows after undefined,
* false if checked follows after undefined.
* @author H. Marlovits
*/
public TristateCheckbox(Composite parent, int style, boolean falseFirst){
super(parent, (style | SWT.CHECK));
addSelectionListener(new TristateSelectionListener(false));
setTristate(START_STATE);
setCyclingOrder(falseFirst);
}
/**
* get the current tristate selection state
*
* @return the current tristate selection state, one of TristateCheckbox.TristateValue
* (TRUE/FALSE/UNDEF)
* @author H. Marlovits
*/
public TristateBoolean getTristate(){
checkWidget();
boolean selection = getSelection();
boolean grayed = getGrayed();
if (selection) {
if (grayed) {
return TristateBoolean.UNDEF;
} else {
return TristateBoolean.TRUE;
}
} else {
return TristateBoolean.FALSE;
}
}
/**
* get the current tristate selection state as String as it will be saved in the db. Usually
* you'll use {@link #getTristate()}.
*
* @return the current tristate selection state as String, one of
* StringConstants.ONE/StringConstants.ZERO/StringConstants.EMPTY
* @author H. Marlovits
*/
public String getTristateDbValue(){
checkWidget();
boolean selection = getSelection();
boolean grayed = getGrayed();
if (selection) {
if (grayed) {
return StringConstants.EMPTY;
} else {
return StringConstants.ONE;
}
} else {
return StringConstants.ZERO;
}
}
/**
* set the tristate selection state
*
* @param newState
* the new tristate selection state, one of TristateCheckbox.TristateValue
* (TRUE/FALSE/UNDEF)
* @author H. Marlovits
*/
public void setTristate(TristateBoolean newState){
checkWidget();
if (newState == TristateBoolean.TRUE) {
setSelection(true);
setGrayed(false);
} else if (newState == TristateBoolean.FALSE) {
setSelection(false);
setGrayed(false);
} else if (newState == TristateBoolean.UNDEF) {
setSelection(true);
setGrayed(true);
}
}
/**
* set the tristate selection state by using a String as it will be saved in the db. Usually
* you'll use {@link #setTristate(TristateBoolean)}.
*
* @param newState
* the new tristate selection as a String value as it is saved in the db, one of
* StringConstants.ONE/StringConstants.ZERO/StringConstants.EMPTY
* @author H. Marlovits
*/
public void setTristateDbValue(String newState){
checkWidget();
if (newState.equalsIgnoreCase(StringConstants.ONE)) {
setSelection(true);
setGrayed(false);
} else if (newState.equalsIgnoreCase(StringConstants.ZERO)) {
setSelection(false);
setGrayed(false);
} else {
setSelection(true);
setGrayed(true);
}
}
/**
* set how the states of the tristate checkbox are cycling through<br>
* undef->false->true OR<br>
* undef->true->false<br>
* default is false first
*
* @author H. Marlovits
*/
public void setCyclingOrder(boolean falseFirst){
this.falseFirst = falseFirst;
}
/**
* I explicitly want to subclass... Shouldn't cause any problems since I just implement a
* constructor calling super and implement some new methods.
*
* @author H. Marlovits
*/
protected void checkSubclass(){}
/**
* a class for implementing a tristate behaviour for checkboxes. Just add this as a selection
* listener to the checkbox. The states cycle through undefined -> checked -> unchecked or
* undefined -> unchecked -> checked
*
* @author H. Marlovits
*/
public class TristateSelectionListener implements SelectionListener {
boolean falseFirst = true;
/**
* Create a new TristateSelectionListener.
*
* @param falseFirst
* specifies the order of the states. if true then false follows after undef, if
* false then true follows after undef
* @author H. Marlovits
*/
TristateSelectionListener(boolean falseFirst){
this.falseFirst = falseFirst;
}
@Override
public void widgetSelected(SelectionEvent e){
Button button = ((Button) e.getSource());
boolean selection = !button.getSelection();
boolean grayed = button.getGrayed();
if (falseFirst) {
if (selection) {
if (grayed) {
button.setSelection(false);
button.setGrayed(false);
} else {
button.setSelection(true);
button.setGrayed(true);
}
} else {
button.setSelection(true);
button.setGrayed(false);
}
} else {
if (selection) {
if (grayed) {
button.setSelection(true);
button.setGrayed(false);
} else {
button.setSelection(false);
button.setGrayed(false);
}
} else {
button.setSelection(true);
button.setGrayed(true);
}
}
}
@Override
public void widgetDefaultSelected(SelectionEvent e){}
}
}