blob: dd651af7b507cb22e3497dd404961b769dc7b12d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 Google, Inc.
* 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:
* Google, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.wb.internal.core.model.property.editor;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalListener;
import org.eclipse.jface.fieldassist.IContentProposalListener2;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.fieldassist.IControlContentAdapter;
import org.eclipse.jface.fieldassist.TextContentAdapter;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.eclipse.wb.internal.core.model.property.Property;
import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
/**
* Abstract {@link PropertyEditor} for that uses {@link Text} as control.
*
* @author scheglov_ke
* @coverage core.model.property.editor
*/
public abstract class AbstractTextPropertyEditor extends TextDisplayPropertyEditor {
////////////////////////////////////////////////////////////////////////////
//
// Editing
//
////////////////////////////////////////////////////////////////////////////
private Text m_textControl;
private boolean m_ignoreFocusLost;
// BEGIN ADT MODIFICATIONS
// ContentProposalAdapter which exposes the openProposalPopup method such
// that we can open the dialog up immediately on focus gain to show all available
// alternatives (the default implementation requires at least one keytroke before
// it shows up)
private class ImmediateProposalAdapter extends ContentProposalAdapter
implements FocusListener, IContentProposalListener, IContentProposalListener2 {
private final PropertyTable m_propertyTable;
private final IContentProposalProvider m_proposalProvider;
public ImmediateProposalAdapter(
Text control,
IControlContentAdapter controlContentAdapter,
IContentProposalProvider proposalProvider,
KeyStroke keyStroke,
char[] autoActivationCharacters,
PropertyTable propertyTable) {
super(control, controlContentAdapter, proposalProvider, keyStroke,
autoActivationCharacters);
m_propertyTable = propertyTable;
m_proposalProvider = proposalProvider;
// On focus gain, start completing
control.addFocusListener(this);
// Listen on popup open and close events, in order to disable
// focus handling on the textfield during those events.
// This is necessary since otherwise as soon as the user clicks
// on the popup with the mouse, the text field loses focus, and
// then instantly closes the popup -- without the selection being
// applied. See for example
// http://dev.eclipse.org/viewcvs/viewvc.cgi/org.eclipse.jface.snippets/
// Eclipse%20JFace%20Snippets/org/eclipse/jface/snippets/viewers/
// Snippet060TextCellEditorWithContentProposal.java?view=markup
// for another example of this technique.
addContentProposalListener((IContentProposalListener) this);
addContentProposalListener((IContentProposalListener2) this);
/* Triggering on empty is disabled for now: it has the unfortunate side-effect
that it's impossible to enter a blank text field - blank matches everything,
so the first item will automatically be selected when you press return.
// If you edit the text and delete everything, the normal implementation
// will close the popup; we'll reopen it
control.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent event) {
if (((Text) getControl()).getText().isEmpty()) {
openIfNecessary();
}
}
});
*/
}
private void openIfNecessary() {
if (m_textControl == null || m_textControl.isDisposed() ||
m_proposalProvider.getProposals(m_textControl.getText(),
m_textControl.getCaretPosition()).length == 0) {
return;
}
getControl().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
if (!isProposalPopupOpen()) {
openProposalPopup();
}
}
});
}
// ---- Implements FocusListener ----
@Override
public void focusGained(FocusEvent event) {
openIfNecessary();
}
@Override
public void focusLost(FocusEvent event) {
}
// ---- Implements IContentProposalListener ----
@Override
public void proposalAccepted(IContentProposal proposal) {
closeProposalPopup();
m_propertyTable.deactivateEditor(true);
}
// ---- Implements IContentProposalListener2 ----
@Override
public void proposalPopupClosed(ContentProposalAdapter adapter) {
m_ignoreFocusLost = false;
}
@Override
public void proposalPopupOpened(ContentProposalAdapter adapter) {
m_ignoreFocusLost = true;
}
}
// END ADT MODIFICATIONS
@Override
public boolean activate(final PropertyTable propertyTable, final Property property, Point location)
throws Exception {
// create Text
{
m_textControl = new Text(propertyTable, SWT.NONE);
@SuppressWarnings("unused")
TextControlActionsManager manager = new TextControlActionsManager(m_textControl);
m_textControl.setEditable(isEditable());
// BEGIN ADT MODIFICATIONS
// Add support for field completion, if the property provides an IContentProposalProvider
// via its the getAdapter method.
IContentProposalProvider completion = property.getAdapter(IContentProposalProvider.class);
if (completion != null) {
ImmediateProposalAdapter adapter = new ImmediateProposalAdapter(
m_textControl, new TextContentAdapter(), completion, null, null,
propertyTable);
adapter.setFilterStyle(ContentProposalAdapter.FILTER_NONE);
adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
ILabelProvider labelProvider = property.getAdapter(ILabelProvider.class);
if (labelProvider != null) {
adapter.setLabelProvider(labelProvider);
}
}
// END ADT MODIFICATIONS
m_textControl.setFocus();
}
// add listeners
m_textControl.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
try {
handleKeyPressed(propertyTable, property, e);
} catch (Throwable ex) {
propertyTable.deactivateEditor(false);
propertyTable.handleException(ex);
}
}
});
m_textControl.addListener(SWT.FocusOut, new Listener() {
@Override
public void handleEvent(Event event) {
if (!m_ignoreFocusLost) {
propertyTable.deactivateEditor(true);
}
}
});
// set data
toWidget(property);
// keep us active
return true;
}
@Override
public final void setBounds(Rectangle bounds) {
m_textControl.setBounds(bounds);
}
@Override
public final void deactivate(PropertyTable propertyTable, Property property, boolean save) {
if (save) {
try {
toProperty(property);
} catch (Throwable e) {
propertyTable.deactivateEditor(false);
propertyTable.handleException(e);
}
}
// dispose Text widget
if (m_textControl != null) {
m_textControl.dispose();
m_textControl = null;
}
}
@Override
public void keyDown(PropertyTable propertyTable, Property property, KeyEvent event)
throws Exception {
boolean withAlt = (event.stateMask & SWT.ALT) != 0;
boolean withCtrl = (event.stateMask & SWT.CTRL) != 0;
if (event.character != 0 && !(withAlt || withCtrl)) {
propertyTable.activateEditor(property, null);
postKeyEvent(SWT.KeyDown, event);
postKeyEvent(SWT.KeyUp, event);
}
}
/**
* Posts low-level {@link SWT.KeyDown} or {@link SWT.KeyUp} event.
*/
private static void postKeyEvent(int type, KeyEvent event) {
Event lowEvent = new Event();
lowEvent.type = type;
lowEvent.keyCode = event.keyCode;
lowEvent.character = event.character;
// post event
Display.getCurrent().post(lowEvent);
}
////////////////////////////////////////////////////////////////////////////
//
// Events
//
////////////////////////////////////////////////////////////////////////////
/**
* Handles {@link KeyListener#keyPressed(KeyEvent)}.
*/
private void handleKeyPressed(PropertyTable propertyTable, Property property, KeyEvent e)
throws Exception {
if (e.keyCode == SWT.CR) {
toProperty(property);
// BEGIN ADT MODIFICATIONS
// After pressing return, dismiss the text cursor to make the value "committed".
// I'm not sure why this is necessary here and not in WindowBuilder proper.
propertyTable.deactivateEditor(true);
// END ADT MODIFICATIONS
} else if (e.keyCode == SWT.ESC) {
propertyTable.deactivateEditor(false);
} else if (e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.ARROW_DOWN) {
e.doit = false;
boolean success = toProperty(property);
// don't allow navigation if current text can not be transferred to property
if (!success) {
return;
}
// OK, deactivate and navigate
propertyTable.deactivateEditor(true);
propertyTable.navigate(e);
}
}
////////////////////////////////////////////////////////////////////////////
//
// Implementation
//
////////////////////////////////////////////////////////////////////////////
private String m_currentText;
/**
* Transfers data from {@link Property} to widget.
*/
private void toWidget(Property property) throws Exception {
// prepare text
String text = getEditorText(property);
if (text == null) {
text = "";
}
// set text
m_currentText = text;
m_textControl.setText(text);
m_textControl.selectAll();
}
/**
* Transfers data from widget to {@link Property}.
*
* @return <code>true</code> if transfer was successful.
*/
private boolean toProperty(Property property) throws Exception {
String text = m_textControl.getText();
// change property only if text was changed
if (!m_currentText.equals(text)) {
m_ignoreFocusLost = true;
try {
boolean success = setEditorText(property, text);
if (!success) {
return false;
}
} finally {
m_ignoreFocusLost = false;
}
// if value was successfully changed, update current text
m_currentText = text;
}
// OK, success
return true;
}
////////////////////////////////////////////////////////////////////////////
//
// Operations
//
////////////////////////////////////////////////////////////////////////////
/**
* @return <code>true</code> if this editor can modify text.
*/
protected boolean isEditable() {
return true;
}
/**
* @return the text to display in {@link Text} control.
*/
protected abstract String getEditorText(Property property) throws Exception;
/**
* Modifies {@link Property} using given text.
*
* @return <code>true</code> if {@link Property} was successfully modified.
*/
protected abstract boolean setEditorText(Property property, String text) throws Exception;
}