/***************************************************************************** * Copyright (c) 2010 CEA LIST. * * 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: * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation *****************************************************************************/ package org.eclipse.papyrus.infra.widgets.editors; import java.util.Timer; import java.util.TimerTask; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.papyrus.infra.widgets.databinding.TextObservableValue; import org.eclipse.papyrus.infra.widgets.selectors.StringSelector; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Text; /** * A Property Editor representing a single-line or multi-line String value * as a Text. * This editor's content is validated when the focus is lost, * or, if the editor is single-line, when the Carriage Return is pressed. * For a multi-line editor, ctrl+enter will also validate the editor's content. * * @see SWT#MULTI * * @author Camille Letavernier */ public class StringEditor extends AbstractValueEditor implements KeyListener, ModifyListener { /** * The text box for editing this editor's value */ protected final Text text; private int delay = 600; private boolean validateOnDelay = false; private final Timer timer; private TimerTask currentValidateTask; private final static int DEFAULT_HEIGHT_HINT = 55; private final static int DEFAULT_WIDTH_HINT = 100; /** * * Constructor. * * @param parent * The composite in which this editor should be displayed * @param style * The style for this editor's text box */ public StringEditor(Composite parent, int style) { this(parent, style, null, DEFAULT_HEIGHT_HINT, DEFAULT_WIDTH_HINT); } /** * * Constructor. * * @param parent * The composite in which this editor should be displayed * @param style * The style for this editor's text box * @param label * The label for this editor */ public StringEditor(Composite parent, int style, String label) { this(parent, style, label, DEFAULT_HEIGHT_HINT, DEFAULT_WIDTH_HINT); } /** * * Constructor. * * @param parent * The composite in which this editor should be displayed * @param style * The style for this editor's text box * @param heighHint * Height hint of the text area in multiline mode * @param widthHint * Width hint of the text area in multiline mode */ public StringEditor(Composite parent, int style, int heighHint, int widthHint) { this(parent, style, null, heighHint, widthHint); } /** * * Constructor. * * @param parent * The composite in which this editor should be displayed * @param style * The style for this editor's text box * @param label * The label for this editor * @param heighHint * Height hint of the text area in multiline mode * @param widthHint * Width hint of the text area in multiline mode */ public StringEditor(Composite parent, int style, String label, int heighHint, int widthHint) { super(parent, label); GridData data = getDefaultLayoutData(); if((style & SWT.MULTI) != 0) { data.heightHint = heighHint; data.widthHint = widthHint; style = style | SWT.V_SCROLL; } text = factory.createText(this, null, style); text.setLayoutData(data); if(label != null) { super.label.setLayoutData(getLabelLayoutData()); } text.addKeyListener(this); setCommitOnFocusLost(text); timer = new Timer(true); } @Override protected GridData getLabelLayoutData() { GridData result = super.getLabelLayoutData(); if(text != null) { if((text.getStyle() & SWT.MULTI) != 0) { result.verticalAlignment = SWT.BEGINNING; } } return result; } /** * Ignored */ public void keyPressed(KeyEvent e) { //Nothing } /** * Validates this editor when one of the following events occur : * - CR released * - Keypad CR released * - Ctrl + [CR | Keypad CR] released * * @see org.eclipse.swt.events.KeyListener#keyReleased(org.eclipse.swt.events.KeyEvent) * * @param e */ //TODO : we should prevent the \n from being written when validating the //multi-line field with Ctrl + CR public void keyReleased(KeyEvent e) { //We listen on Carriage Return or Ctrl+ Carriage return, depending on //whether the editor is single- or multi-line if(e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { if((text.getStyle() & SWT.MULTI) == 0) { //Single-line : Enter if(e.stateMask == SWT.NONE) { notifyChange(); } } else { //Multi-line : Ctrl+Enter if(e.stateMask == SWT.CTRL) { String str = text.getText(); if(str.endsWith(StringSelector.LINE_SEPARATOR)) { int newLength = str.length() - StringSelector.LINE_SEPARATOR.length(); text.setText(str.substring(0, newLength)); text.setSelection(newLength); } notifyChange(); } } } } @Override public void setModelObservable(IObservableValue observable) { setWidgetObservable(new TextObservableValue(text, observable, SWT.FocusOut), true); super.setModelObservable(observable); } /** * {@inheritDoc} */ @Override public Object getEditableType() { return String.class; } /** * {@inheritDoc} */ @Override public Object getValue() { return text.getText(); } @Override public void setReadOnly(boolean readOnly) { text.setEnabled(!readOnly); } @Override public boolean isReadOnly() { return !text.isEnabled(); } protected void notifyChange() { text.notifyListeners(SWT.FocusOut, new Event()); commit(); } @Override public void setToolTipText(String tooltip) { text.setToolTipText(tooltip); super.setLabelToolTipText(tooltip); } /** * Sets the current text value for this editor * * @param value */ public void setValue(Object value) { if(value instanceof String) { this.text.setText((String)value); } else { this.text.setText(""); //$NON-NLS-1$; } } /** * Indicates that this editor should be automatically validated after * a timer. * * @param validateOnDelay */ public void setValidateOnDelay(boolean validateOnDelay) { this.validateOnDelay = validateOnDelay; if(validateOnDelay) { text.addModifyListener(this); } else { text.removeModifyListener(this); cancelCurrentTask(); } } /** * Indicates that this editor should be automatically validated after * the given timer * * @param millis * The delay after which the editor should be automatically validated, * in milliseconds. The default is 600ms */ public void setValidateOnDelay(int millis) { this.delay = millis; setValidateOnDelay(true); if(delay == 0) { cancelCurrentTask(); } } private void cancelCurrentTask() { if(currentValidateTask != null) { currentValidateTask.cancel(); } } /** * {@inheritDoc} */ public void modifyText(ModifyEvent e) { //SWT Thread if(validateOnDelay) { if(delay == 0) { commit(); //Direct commit on edition, to avoid creating useless threads return; } cancelCurrentTask(); currentValidateTask = new TimerTask() { //Timer thread @Override public void run() { StringEditor.this.getDisplay().syncExec(new Runnable() { //SWT Thread public void run() { commit(); } }); } }; timer.schedule(currentValidateTask, delay); } } @Override public void dispose() { cancelCurrentTask(); timer.cancel(); super.dispose(); } }