package org.csstudio.sds.ui.internal.editor.newproperties;
import java.util.List;
import org.csstudio.sds.model.AbstractWidgetModel;
import org.csstudio.sds.model.WidgetProperty;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.bindings.keys.ParseException;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.fieldassist.TextContentAdapter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
/**
* Base class for sections which use a text field for editing. Subclasses can
* easily equip the text field with context sensitive auto completion texts if
* needed.
*
* @author Sven Wende
*
* @param <E>
* the type of {@link WidgetProperty} that will be edited using this
* section
*
* @param <V>
* the type of values that can be set to the widget property
*
*/
public abstract class AbstractTextSection<E extends WidgetProperty, V> extends
AbstractBaseSection<E> {
private Text textControl;
public AbstractTextSection(final String propertyId) {
super(propertyId);
}
/**
* Returns the text control.
*
* @return the text control
*/
protected Text getTextControl() {
return textControl;
}
/**
* Returns the SWT styles for the text control. Default is
* {@link SWT#SINGLE} and {@link SWT#NO_SCROLL}. Subclasses may override.
*
* @return the SWT styles for the text control
*/
protected int getTextControlStyle() {
return SWT.SINGLE | SWT.NO_SCROLL;
}
/**
* Returns the minimum width for the text control. Default is
* {@link AbstractBaseSection#STANDARD_WIDGET_WIDTH}. Subclasses may
* override.
*
* @return the minimum width for the text control
*/
protected int getMinimumWidth() {
return STANDARD_WIDGET_WIDTH;
}
/**
* Sets the current text.
*
* @param text
* the text
*/
protected void setCurrentText(final String text) {
textControl.setText(text);
}
/**
* Returns the current text.
*/
protected String getCurrentText() {
return textControl.getText();
}
public int getTextHeight() {
return super.getMinimumHeight();
}
/**
* Template method. Subclassed need to return a converted value which can be
* applied to the widget property.
*
* @param text
* the current text
*
* @return a domain object which can be set as value for the underlying
* widget property
*/
protected abstract V getConvertedValue(String text);
/**
*{@inheritDoc}
*/
@Override
protected void doCreateControls(final Composite parent,
final TabbedPropertySheetPage tabbedPropertySheetPage) {
parent.setLayout(new FormLayout());
// .. the text control
textControl = getWidgetFactory().createText(parent, null,
getTextControlStyle());
FormData fd = new FormData();
fd.left = new FormAttachment(0, 0);
fd.right = new FormAttachment(50, 0);
fd.height = getTextHeight();
textControl.setLayoutData(fd);
// .. key listeners (stop editing on ESCAPE and apply value on ENTER and
// TAB)
textControl.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(final KeyEvent e) {
switch (e.keyCode) {
case SWT.ESC:
cancelEditing();
break;
case SWT.TAB:
e.doit = false;
applyPropertyChange(getConvertedValue(getCurrentText()));
setFocusToNextSibling(parent);
break;
case SWT.CR:
case SWT.KEYPAD_CR:
if (e.stateMask != SWT.MOD1) {
e.doit = false;
applyPropertyChange(getConvertedValue(getCurrentText()));
setFocusToNextSibling(parent);
}
break;
}
}
});
// .. highlight text control, when it has the focus
final Color color = textControl.getBackground();
textControl.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(final FocusEvent e) {
applyPropertyChange(getConvertedValue(getCurrentText()));
textControl.setBackground(color);
}
@Override
public void focusGained(final FocusEvent e) {
textControl.setBackground(COLOR_CONTROL_ACTIVE);
}
});
// .. configure the content proposal adapter
ContentProposalAdapter adapter = new ContentProposalAdapter(
textControl, new TextContentAdapter(),
new IContentProposalProvider() {
@Override
public IContentProposal[] getProposals(final String contents,
final int position) {
List<IContentProposal> proposals = getContentProposals(
mainWidgetProperty, selectedWidget,
selectedWidgets);
return proposals.toArray(new IContentProposal[proposals
.size()]);
}
}, getContentProposalActivationKeystroke(),
getContentProposalActivationCharacters());
adapter.setPropagateKeys(true);
adapter.setProposalAcceptanceStyle(getProposalAcceptanceStyle());
adapter.setPopupSize(new Point(400, 300));
}
/**
* Returns the characters which activate the content proposal popup menu.
* Default is "$" Subclasses may override.
*
* @return the characters which activate the content proposal popup menu
*/
protected char[] getContentProposalActivationCharacters() {
return new char[] { '$' };
}
/**
* Returns the keystroke which activates the content proposal popup menu.
* Default is CTRL+Space Subclasses may override.
*
* @return the keystroke which activates the content proposal popup menu
*/
protected KeyStroke getContentProposalActivationKeystroke() {
KeyStroke keyStroke;
try {
keyStroke = KeyStroke.getInstance("Ctrl+Space");
} catch (ParseException e1) {
keyStroke = null;
}
return keyStroke;
}
/**
* Returns the insertion behavior of the {@link ContentProposalAdapter}.
* One of {@link ContentProposalAdapter#PROPOSAL_IGNORE},
* {@link ContentProposalAdapter#PROPOSAL_INSERT} or
* {@link ContentProposalAdapter#PROPOSAL_REPLACE};
*
* @return The style for the insertion behavior
*/
protected int getProposalAcceptanceStyle() {
return ContentProposalAdapter.PROPOSAL_REPLACE;
}
/**
* Template method. Subclasses may return content proposals for the current
* input elements.
*
* @param property
* the property
* @param selectedWidget
* the primary selected widget
* @param selectedWidgets
* all selected widgets
* @return
*/
protected abstract List<IContentProposal> getContentProposals(E property,
AbstractWidgetModel selectedWidget,
List<AbstractWidgetModel> selectedWidgets);
/**
* Move focus to the next neighbor.
*
* @param c
* the current control
* @param next
* @return
*/
private boolean setFocusToNextSibling(final Control c) {
Composite parent = c.getParent();
Control[] children = parent.getTabList();
for (int i = 0; i < children.length; i++) {
Control child = children[i];
if (child == c) {
for (int j = i + 1; j < children.length; j++) {
Control nc = children[j];
if (nc.setFocus()) {
return false;
}
}
}
}
return false;
}
}