/*
* Carrot2 project.
*
* Copyright (C) 2002-2016, Dawid Weiss, Stanisław Osiński.
* All rights reserved.
*
* Refer to the full license file "carrot2.LICENSE"
* in the root folder of the repository checkout or at:
* http://www.carrot2.org/carrot2.LICENSE
*/
package org.carrot2.workbench.editors.impl;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.carrot2.workbench.core.helpers.GUIFactory;
import org.carrot2.workbench.editors.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.carrot2.shaded.guava.common.collect.BiMap;
/**
* Template code for editors with mapped values (String to Object).
*/
public abstract class MappedValueComboEditor extends AttributeEditorAdapter
{
/**
* Special value for no-selection.
*/
private final static String NULL_VALUE = "";
/**
* Mapping between values and their user-interface representations.
*/
private BiMap<? extends Object, String> valueToName;
/**
* Order of values on the suggestion list.
*/
private List<Object> valueOrder;
/**
* A box container.
*/
private Composite boxContainer;
/**
* A {@link Combo} component for displaying mapped constants and hints.
*/
private Combo box;
/**
* Most recently selected value;
*/
private Object currentValue;
/**
* If <code>true</code> valid value selection is required (the attribute cannot be
* <code>null</code>).
*/
public boolean valueRequired = true;
/**
* If <code>true</code>, the attribute can take any {@link String} value, not only
* those listed in {@link #valueOrder} and other collection fields.
*/
public boolean anyValueAllowed = false;
@Override
protected AttributeEditorInfo init(Map<String,Object> defaultValues)
{
return new AttributeEditorInfo(1, false);
}
/**
* Set mapped values and their user-interface equivalents.
*/
protected final void setMappedValues(BiMap<? extends Object, String> valueToName,
List<Object> valueOrder)
{
this.valueToName = valueToName;
this.valueOrder = valueOrder;
if (this.box != null)
{
final Object prev = getValue();
recreate();
setValue(prev);
}
}
/*
*
*/
public void createEditor(Composite parent, int gridColumns)
{
boxContainer = new Composite(parent, SWT.NONE);
boxContainer.setLayoutData(GUIFactory.editorGridData().grab(true, false).hint(200,
SWT.DEFAULT).align(SWT.FILL, SWT.CENTER).span(gridColumns, 1).create());
boxContainer.setLayout(new FillLayout());
recreate();
}
/*
*
*/
private void recreate()
{
if (box != null)
{
box.dispose();
}
final int style = SWT.DROP_DOWN | SWT.BORDER
| (anyValueAllowed ? 0 : SWT.READ_ONLY);
box = new Combo(boxContainer, style);
/*
* React to focus lost.
*/
final Class<?> clazz = getClass();
box.addFocusListener(new FocusAdapter()
{
public void focusLost(FocusEvent e)
{
checkContentChange();
}
});
box.addModifyListener(new ModifyListener()
{
public void modifyText(ModifyEvent e)
{
/*
* If any value is allowed in the combo box,
* send intermediate content changing events. Otherwise
* send full change event.
*/
if (anyValueAllowed)
{
fireContentChanging(new AttributeEvent(clazz, getAttributeKey(),
getBoxValue()));
}
else
{
checkContentChange();
}
}
});
box.addTraverseListener(new TraverseListener()
{
public void keyTraversed(TraverseEvent e)
{
if (e.detail == SWT.TRAVERSE_RETURN)
{
checkContentChange();
}
}
});
if (!valueRequired)
{
/*
* Add an artificial option to the suggestion list to clear selection.
*/
box.add(NULL_VALUE);
}
/*
* Add hints.
*/
for (Object value : valueOrder)
{
box.add(valueToName.get(value));
}
boxContainer.layout(true);
currentValue = null;
}
/**
* Map a given attribute value to user-friendly name.
*/
private Object userFriendlyToValue(String text)
{
if (text == NULL_VALUE || StringUtils.isEmpty(text))
{
return null;
}
Object value = this.valueToName.inverse().get(text);
if (value == null && this.anyValueAllowed)
{
value = text;
}
return value;
}
/*
*
*/
private Object getBoxValue()
{
int selection = box.getSelectionIndex();
if (selection > 0)
{
if (selection == 0 && !this.valueRequired)
{
return null;
}
else
{
if (!valueRequired)
{
selection -= 1;
}
return valueOrder.get(selection);
}
}
else
{
final String text = box.getText();
return userFriendlyToValue(text);
}
}
/*
*
*/
@Override
public void setFocus()
{
this.box.setFocus();
}
/*
*
*/
@Override
public Object getValue()
{
return currentValue;
}
/*
*
*/
@Override
public void setValue(Object newValue)
{
if (ObjectUtils.equals(newValue, getValue()))
{
return;
}
if (newValue == null)
{
box.deselectAll();
}
else
{
String value = this.valueToName.get(newValue);
if (value != null)
{
box.setText(value);
}
else
{
if (anyValueAllowed)
{
box.setText(newValue.toString());
}
else
{
box.deselectAll();
}
}
}
checkContentChange();
}
/**
* Check if the content has changed compared to the current value of this attribute.
* If so, fire an event.
*/
private void checkContentChange()
{
final Object asValue = getBoxValue();
if (!ObjectUtils.equals(currentValue, asValue))
{
this.currentValue = asValue;
fireAttributeChanged(new AttributeEvent(this));
}
}
}