/*******************************************************************************
* Copyright (c) 2004, 2010 BREDEX GmbH.
* 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:
* BREDEX GmbH - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.jubula.client.ui.rcp.widgets;
import java.text.MessageFormat;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jubula.client.core.businessprocess.IComponentNameCache;
import org.eclipse.jubula.client.ui.rcp.i18n.Messages;
import org.eclipse.jubula.client.ui.utils.ErrorHandlingUtil;
import org.eclipse.jubula.tools.internal.constants.CharacterConstants;
import org.eclipse.jubula.tools.internal.constants.StringConstants;
import org.eclipse.jubula.tools.internal.exception.Assert;
import org.eclipse.jubula.tools.internal.messagehandling.MessageIDs;
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.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
/**
* A cell editor that manages a m_popupText entry field.
* The cell editor's value is the m_popupText string itself.
* <p>
* This class may be instantiated; it is not intended to be subclassed.
* </p>
*/
public class CompNamePopupTextCellEditor extends CellEditor {
/**
* Default TextCellEditor style
* specify no borders on m_popupText widget as cell outline in table already
* provides the look of a border.
*/
private static final int DEFAULT_STYLE = SWT.SINGLE;
/**
* The m_popupText control; initially <code>null</code>.
*/
private CompNamePopUpTextField m_popupText;
/***/
private ModifyListener m_modifyListener;
/**
* State information for updating action enablement
*/
private boolean m_isSelection = false;
/***/
private boolean m_isDeleteable = false;
/***/
private boolean m_isSelectable = false;
/** cache of last valid stored value */
private String m_lastValidValue;
/** the component cache to use for finding and modifying components */
private IComponentNameCache m_compCache;
/**
* Creates a new m_popupText string cell editor parented under the given control.
* The cell editor value is the string itself, which is initially the empty string.
* Initially, the cell editor has no cell validator.
*
* @param compCache The Component Names cache to use.
* @param parent the parent control
*/
public CompNamePopupTextCellEditor(IComponentNameCache compCache,
Composite parent) {
super(parent, DEFAULT_STYLE);
setComponentNameCache(compCache);
m_lastValidValue = StringConstants.EMPTY;
}
/**
* Checks to see if the "deleteable" state (can delete/
* nothing to delete) has changed and if so fire an
* enablement changed notification.
*/
private void checkDeleteable() {
boolean oldIsDeleteable = m_isDeleteable;
m_isDeleteable = isDeleteEnabled();
if (oldIsDeleteable != m_isDeleteable) {
fireEnablementChanged(DELETE);
}
}
/**
* Checks to see if the "selectable" state (can select)
* has changed and if so fire an enablement changed notification.
*/
private void checkSelectable() {
boolean oldIsSelectable = m_isSelectable;
m_isSelectable = isSelectAllEnabled();
if (oldIsSelectable != m_isSelectable) {
fireEnablementChanged(SELECT_ALL);
}
}
/**
* @param filter the compType name (display-version)
*/
public void setFilter(String filter) {
m_popupText.setFilter(filter);
}
/**
* Checks to see if the selection state (selection /
* no selection) has changed and if so fire an
* enablement changed notification.
*/
private void checkSelection() {
boolean oldIsSelection = m_isSelection;
m_isSelection = m_popupText.getSelectionCount() > 0;
if (oldIsSelection != m_isSelection) {
fireEnablementChanged(COPY);
fireEnablementChanged(CUT);
}
}
/**
* Method declared on CellEditor.
* @param parent The parent composite.
* @return The control.
*/
protected Control createControl(Composite parent) {
m_popupText = new CompNamePopUpTextField(
m_compCache, parent, getStyle());
m_popupText.addSelectionListener(new SelectionAdapter() {
public void widgetDefaultSelected(SelectionEvent e) {
handleDefaultSelection();
}
});
m_popupText.addKeyListener(new KeyAdapter() {
// hook key pressed - see PR 14201
public void keyPressed(KeyEvent e) {
keyReleaseOccured(e);
// as a result of processing the above call, clients may have
// disposed this cell editor
if ((getControl() == null) || getControl().isDisposed()) {
return;
}
checkSelection(); // see explanation below
checkDeleteable();
checkSelectable();
}
});
m_popupText.addTraverseListener(new TraverseListener() {
public void keyTraversed(TraverseEvent e) {
if (e.detail == SWT.TRAVERSE_ESCAPE
|| e.detail == SWT.TRAVERSE_RETURN) {
e.doit = false;
}
}
});
// We really want a selection listener but it is not supported so we
// use a key listener and a mouse listener to know when selection changes
// may have occured
m_popupText.addMouseListener(new MouseAdapter() {
public void mouseUp(MouseEvent e) {
checkSelection();
checkDeleteable();
checkSelectable();
}
});
m_popupText.addFocusListener(new FocusAdapter() {
public void focusLost(FocusEvent e) {
CompNamePopupTextCellEditor.this.focusLost();
}
public void focusGained(FocusEvent e) {
super.focusGained(e);
}
});
m_popupText.setFont(parent.getFont());
m_popupText.setBackground(parent.getBackground());
m_popupText.setText(StringConstants.EMPTY);
m_popupText.addModifyListener(getModifyListener());
return m_popupText;
}
/**
* The <code>TextCellEditor</code> implementation of
* this <code>CellEditor</code> framework method returns
* the m_popupText string.
*
* @return the m_popupText string
*/
protected Object doGetValue() {
return m_popupText.getText();
}
/**
* Method declared on CellEditor.
*/
protected void doSetFocus() {
if (m_popupText != null) {
m_popupText.selectAll();
m_popupText.setFocus();
checkSelection();
checkDeleteable();
checkSelectable();
}
}
/**
* The <code>TextCellEditor</code> implementation of
* this <code>CellEditor</code> framework method accepts
* a m_popupText string (type <code>String</code>).
*
* @param value a m_popupText string (type <code>String</code>)
*/
protected void doSetValue(Object value) {
Assert.verify(m_popupText != null);
String v = (String)value;
if (v == null) {
v = StringConstants.EMPTY;
}
m_popupText.removeModifyListener(getModifyListener());
m_popupText.setText(v);
m_popupText.setData(CompNamePopUpTextField.INITPOPUP, false);
m_popupText.addModifyListener(getModifyListener());
m_lastValidValue = v;
}
/**
* Processes a modify event that occurred in this m_popupText cell editor.
* This framework method performs validation and sets the error message
* accordingly, and then reports a change via <code>fireEditorValueChanged</code>.
* Subclasses should call this method at appropriate times. Subclasses
* may extend or reimplement.
*
*/
protected void editOccured() {
String value = m_popupText.getText();
if (value == null) {
value = StringConstants.EMPTY;
}
boolean oldValidState = isValueValid();
boolean newValidState = isCorrect(value);
if (value == null && newValidState) {
Assert.verify(false,
Messages.ValidatorIsntLimitingTheCellEditorsTypeRange);
}
if (!newValidState) {
// try to insert the current value into the error message.
setErrorMessage(MessageFormat.format(getErrorMessage(),
new Object[] { value }));
}
valueChanged(oldValidState, newValidState);
}
/**
*
* {@inheritDoc}
*/
protected void fireApplyEditorValue() {
final String errorMsg = getErrorMessage();
if (errorMsg != null) {
// Show error message
setValue(m_lastValidValue);
ErrorHandlingUtil.createMessageDialog(
MessageIDs.E_INCOMPATIBLE_COMP_TYPE, null,
new String[] {errorMsg});
}
super.fireApplyEditorValue();
}
/**
* Since a m_popupText editor field is scrollable we don't
* set a minimumSize.
* @return LayoutData.
*/
public LayoutData getLayoutData() {
return new LayoutData();
}
/**
* Return the modify listener.
* @return the modify listener.
*/
private ModifyListener getModifyListener() {
if (m_modifyListener == null) {
m_modifyListener = new ModifyListener() {
public void modifyText(ModifyEvent e) {
editOccured();
}
};
}
return m_modifyListener;
}
/**
* Handles a default selection event from the m_popupText control by applying the editor
* value and deactivating this cell editor.
*
*/
protected void handleDefaultSelection() {
// same with enter-key handling code in keyReleaseOccured(e);
fireApplyEditorValue();
deactivate();
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method returns <code>true</code> if
* the current selection is not empty.
* @return <code>true</code> if
* the current selection is not empty.
*/
public boolean isCopyEnabled() {
if (m_popupText == null || m_popupText.isDisposed()) {
return false;
}
return m_popupText.getSelectionCount() > 0;
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method returns <code>true</code> if
* the current selection is not empty.
* @return <code>true</code> if
* the current selection is not empty.
*/
public boolean isCutEnabled() {
if (m_popupText == null || m_popupText.isDisposed()) {
return false;
}
return m_popupText.getSelectionCount() > 0;
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method returns <code>true</code>
* if there is a selection or if the caret is not positioned
* at the end of the m_popupText.
* @return <code>true</code>
* if there is a selection or if the caret is not positioned
* at the end of the m_popupText.
*/
public boolean isDeleteEnabled() {
if (m_popupText == null || m_popupText.isDisposed()) {
return false;
}
return m_popupText.getSelectionCount() > 0
|| m_popupText.getCaretPosition() < m_popupText.getCharCount();
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method always returns <code>true</code>.
* @return <code>true</code>.
*/
public boolean isPasteEnabled() {
return !(m_popupText == null || m_popupText.isDisposed());
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method always returns <code>true</code>.
* @return <code>true</code>.
*/
public boolean isSaveAllEnabled() {
return !(m_popupText == null || m_popupText.isDisposed());
}
/**
* Returns <code>true</code> if this cell editor is
* able to perform the select all action.
* <p>
* This default implementation always returns
* <code>false</code>.
* </p>
* <p>
* Subclasses may override
* </p>
* @return <code>true</code> if select all is possible,
* <code>false</code> otherwise
*/
public boolean isSelectAllEnabled() {
if (m_popupText == null || m_popupText.isDisposed()) {
return false;
}
return m_popupText.getCharCount() > 0;
}
/**
* Processes a key release event that occurred in this cell editor.
* <p>
* The <code>TextCellEditor</code> implementation of this framework method
* ignores when the RETURN key is pressed since this is handled in
* <code>handleDefaultSelection</code>.
* An exception is made for Ctrl+Enter for multi-line texts, since
* a default selection event is not sent in this case.
* </p>
*
* @param keyEvent the key event
*/
protected void keyReleaseOccured(KeyEvent keyEvent) {
if (keyEvent.character == CharacterConstants.RETURN) {
// Enter is handled in handleDefaultSelection.
// Do not apply the editor value in response to an Enter key event
// since this can be received from the IME when the intent is -not-
// to apply the value.
// See bug http://eclip.se/39074 [CellEditors] [DBCS] canna input mode fires bogus event from Text Control
//
// An exception is made for Ctrl+Enter for multi-line texts, since
// a default selection event is not sent in this case.
if (m_popupText != null && !m_popupText.isDisposed()
&& (m_popupText.getStyle() & SWT.MULTI) != 0
&& (keyEvent.stateMask & SWT.CTRL) != 0) {
super.keyReleaseOccured(keyEvent);
}
return;
}
super.keyReleaseOccured(keyEvent);
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method copies the
* current selection to the clipboard.
*/
public void performCopy() {
m_popupText.copy();
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method cuts the
* current selection to the clipboard.
*/
public void performCut() {
m_popupText.cut();
checkSelection();
checkDeleteable();
checkSelectable();
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method deletes the
* current selection or, if there is no selection,
* the character next character from the current position.
*/
public void performDelete() {
if (m_popupText.getSelectionCount() > 0) {
// remove the contents of the current selection
m_popupText.insert(StringConstants.EMPTY);
} else {
// remove the next character
int pos = m_popupText.getCaretPosition();
if (pos < m_popupText.getCharCount()) {
m_popupText.setSelection(pos, pos + 1);
m_popupText.insert(StringConstants.EMPTY);
}
}
checkSelection();
checkDeleteable();
checkSelectable();
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method pastes the
* the clipboard contents over the current selection.
*/
public void performPaste() {
m_popupText.paste();
checkSelection();
checkDeleteable();
checkSelectable();
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method selects all of the
* current m_popupText.
*/
public void performSelectAll() {
m_popupText.selectAll();
checkSelection();
checkDeleteable();
}
/**
* @param isSelectable set the cellEditor selectable
*/
public void setSelectable(boolean isSelectable) {
m_isSelectable = isSelectable;
}
/**
*
* @param compCache The new Component Name cache to use.
*/
public void setComponentNameCache(IComponentNameCache compCache) {
m_popupText.setComponentNameCache(compCache);
m_compCache = compCache;
}
/**
* {@inheritDoc}
*/
protected void focusLost() {
if (!m_popupText.isPopupOpen()) {
super.focusLost();
}
}
/**
* {@inheritDoc}
*/
@Override
protected boolean dependsOnExternalFocusListener() {
// Always return false;
// Otherwise, the ColumnViewerEditor will install an additional
// focus listener
// that cancels cell editing on focus lost, even if focus gets lost
// due to
// activation of the completion proposal popup. See also bug
// http://eclip.se/58777.
return false;
}
}