/*
* Beanfabrics Framework Copyright (C) by Michael Karneim, beanfabrics.org
* Use is subject to license terms. See license.txt.
*/
// TODO javadoc - remove this comment only when the class and all non-public
// methods and fields are documented
package org.beanfabrics.swing.internal;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import javax.swing.AbstractListModel;
import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import org.beanfabrics.View;
import org.beanfabrics.event.WeakPropertyChangeListener;
import org.beanfabrics.model.AbstractPM;
import org.beanfabrics.model.ITextPM;
import org.beanfabrics.model.Options;
import org.beanfabrics.model.PresentationModel;
import org.beanfabrics.swing.BnComboBoxEditor;
import org.beanfabrics.swing.ErrorIconPainter;
import org.beanfabrics.swing.KeyBindingProcessor;
/**
* The <code>TextPMComboBox</code> is a {@link JComboBox} that is a view on an {@link ITextPM}.
*
* @author Michael Karneim
* @author Max Gensthaler
*/
@SuppressWarnings("serial")
public class TextPMComboBox extends JComboBox implements KeyBindingProcessor, View<ITextPM> {
private static final Point DEFAULT_ERROR_ICON_OFFSET = new Point(-20, 0);
private ITextPM pModel;
private ErrorIconPainter errorIconPainter = createDefaultErrorIconPainter();
private final PropertyChangeListener propertyListener = new MyWeakPropertyChangeListener();
private class MyWeakPropertyChangeListener implements WeakPropertyChangeListener, Serializable {
public void propertyChange(PropertyChangeEvent evt) {
refresh();
((TextEditorComboBoxModel) getModel()).refresh(); // informs the textPM listeners (gui)
}
}
private final ActionListener clearAction = new ClearActionListener();
private class ClearActionListener implements ActionListener, Serializable {
public void actionPerformed(ActionEvent e) {
int items = getItemCount();
for (int i = 0; i < items; ++i) {
Object item = getItemAt(i);
if ("".trim().equals(item)) {
setSelectedIndex(i);
break;
}
}
}
}
public TextPMComboBox() {
this.setEnabled(false);
this.setModel(this.createDefaultModel());
this.registerKeyboardAction(clearAction, KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0),
JComponent.WHEN_FOCUSED);
this.registerKeyboardAction(clearAction, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), JComponent.WHEN_FOCUSED);
}
public TextPMComboBox(ITextPM pModel) {
this();
setPresentationModel(pModel);
}
/** {@inheritDoc} */
public ITextPM getPresentationModel() {
return pModel;
}
/** {@inheritDoc} */
public void setPresentationModel(ITextPM newModel) {
ITextPM oldModel = this.pModel;
if (this.pModel != null) {
this.pModel.removePropertyChangeListener(propertyListener);
}
this.pModel = newModel;
if (this.pModel != null) {
this.pModel.addPropertyChangeListener(propertyListener);
this.setEditor(createBnComboBoxEditor());
}
refresh();
this.firePropertyChange("presentationModel", oldModel, newModel);
}
/**
* Constructs a new {@link BnComboBoxEditor} for this <code>BnComboBox</code>. This new
* <code>BnComboBoxEditor</code> is used for editing the value of this <code>BnComboBox</code>.
*
* @return a new {@link BnComboBoxEditor}
*/
protected BnComboBoxEditor createBnComboBoxEditor() {
return new BnComboBoxEditor(this);
}
/**
* Returns whether this component is connected to the target {@link PresentationModel} to synchronize with. This is
* a convenience method.
*
* @return <code>true</code> when this component is connected, else <code>false</code>
*/
public boolean isConnected() {
return this.pModel != null;
}
/**
* Returns whether this component is connected to a PM that provides {@link Options}.
*
* @return <code>true</code> when this component has access to some {@link Options}
*/
protected boolean hasOptions() {
return isConnected() && pModel.getOptions() != null;
}
/**
* Configures this component depending on the target {@link AbstractPM}s attributes.
*/
protected void refresh() {
final ITextPM pModel = this.getPresentationModel();
if (pModel != null) {
this.setEnabled(pModel.isEditable());
this.setToolTipText(pModel.isValid() == false ? pModel.getValidationState().getMessage() : pModel
.getDescription());
} else {
this.setToolTipText(null);
this.setEnabled(false);
}
this.repaint();
}
protected TextEditorComboBoxModel createDefaultModel() {
return new TextEditorComboBoxModel();
}
private ErrorIconPainter createDefaultErrorIconPainter() {
ErrorIconPainter result = new ErrorIconPainter();
result.setOffset(DEFAULT_ERROR_ICON_OFFSET);
return result;
}
public ErrorIconPainter getErrorIconPainter() {
return errorIconPainter;
}
public void setErrorIconPainter(ErrorIconPainter aErrorIconPainter) {
if (aErrorIconPainter == null) {
throw new IllegalArgumentException("aErrorIconPainter == null");
}
this.errorIconPainter = aErrorIconPainter;
}
/** {@inheritDoc} */
@Override
public void paintChildren(Graphics g) {
super.paintChildren(g);
if (shouldPaintErrorIcon()) {
errorIconPainter.paint(g, this);
}
}
private boolean shouldPaintErrorIcon() {
if (this.isEditable()) {
return false; // editable => error icon gets painted by BnComboBoxEditor
}
ITextPM pModel = this.getPresentationModel();
if (pModel == null) {
return false;
}
return (pModel.isValid() == false);
}
/** {@inheritDoc} */
public boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
boolean result = super.processKeyBinding(ks, e, condition, pressed);
if (result == false) {
selectWithKeyChar(ks.getKeyChar());
}
return result;
}
protected class TextEditorComboBoxModel extends AbstractListModel implements ComboBoxModel {
public Object getElementAt(int index) {
if (isConnected() == false) {
return null;
}
if (index < 0 || index >= pModel.getOptions().size()) {
return null;
}
return pModel.getOptions().getValue(index);
}
public int getSize() {
if (isConnected() == false)
return 0;
if (pModel.getOptions() == null)
return 0;
return pModel.getOptions().size();
}
public Object getSelectedItem() {
if (isConnected() == false)
return null;
return pModel.getText();
}
public void setSelectedItem(Object anItem) {
if (anItem == null && this.getSelectedItem() == null)
return;
if (anItem != null && anItem.equals(this.getSelectedItem())) {
return;
}
pModel.setText(anItem != null ? anItem.toString() : "");
this.fireContentsChanged(this, -1, -1);
}
public void refresh() {
this.fireContentsChanged(this, -1, -1);
}
}
}