blob: 937e9b8ea16ad2f86b278b148e09d5391b2f20a9 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2005, 2018 IBM Corporation and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0.
#
# SPDX-License-Identifier: EPL-2.0
#
# Contributors:
# IBM Corporation - org.eclipse.jdt: initial API and implementation
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.ltk.ui.sourceediting;
import org.eclipse.core.databinding.observable.Diffs;
import org.eclipse.core.databinding.observable.IObservable;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.databinding.observable.value.AbstractVetoableValue;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
/**
* {@link IObservable} implementation that wraps a {@link SnippetEditor}. The time at which
* listeners should be notified about changes to the text is specified on construction.
*
* <dl>
* <dt>Events:</dt>
* <dd>If the update event type (specified on construction) is <code>SWT.Modify</code> a value
* change event will be fired on every key stroke. If the update event type is
* <code>SWT.FocusOut</code> a value change event will be fired on focus out. When in either
* mode if the user is entering text and presses [Escape] the value will be reverted back to
* the last value set using doSetValue(). Regardless of the update event type a value
* changing event will fire on verify to enable vetoing of changes.</dd>
* </dl>
*/
public class SnippetEditorObservable extends AbstractVetoableValue {
/**
* {@link Text} widget that this is being observed.
*/
private final StyledText text;
/**
* Flag to track when the model is updating the widget. When
* <code>true</code> the handlers for the SWT events should not process
* the event as this would cause an infinite loop.
*/
private boolean updating= false;
/**
* SWT event that on firing this observable will fire change events to its
* listeners.
*/
private final int updateEventType;
/**
* Valid types for the {@link #updateEventType}.
*/
private static final int[] validUpdateEventTypes= new int[] { SWT.Modify,
SWT.FocusOut, SWT.None };
/**
* Previous value of the Text.
*/
private String oldValue;
private final Listener updateListener= new Listener() {
@Override
public void handleEvent(final Event event) {
if (!SnippetEditorObservable.this.updating) {
final String newValue= SnippetEditorObservable.this.text.getText();
if (!newValue.equals(SnippetEditorObservable.this.oldValue)) {
fireValueChange(Diffs.createValueDiff(SnippetEditorObservable.this.oldValue, newValue));
SnippetEditorObservable.this.oldValue= newValue;
}
}
}
};
private final VerifyListener verifyListener;
/**
* Constructs a new instance.
*
* @param realm can not be <code>null</code>
* @param text
* @param updateEventType
*/
public SnippetEditorObservable(final Realm realm, final SnippetEditor editor,
final int updateEventType) {
super(realm);
boolean eventValid= false;
for (int i= 0; !eventValid && i < validUpdateEventTypes.length; i++) {
eventValid= (updateEventType == validUpdateEventTypes[i]);
}
if (!eventValid) {
throw new IllegalArgumentException(
"UpdateEventType [" + updateEventType + "] is not supported."); //$NON-NLS-1$//$NON-NLS-2$
}
this.text= editor.getSourceViewer().getTextWidget();
this.updateEventType= updateEventType;
if (updateEventType != SWT.None) {
this.text.addListener(updateEventType, this.updateListener);
}
this.oldValue= this.text.getText();
this.verifyListener= new VerifyListener() {
@Override
public void verifyText(final VerifyEvent e) {
if (!SnippetEditorObservable.this.updating) {
final String currentText= SnippetEditorObservable.this.text
.getText();
final String newText= currentText.substring(0, e.start) + e.text
+ currentText.substring(e.end);
if (!fireValueChanging(Diffs.createValueDiff(currentText,
newText))) {
e.doit= false;
}
}
}
};
this.text.addVerifyListener(this.verifyListener);
this.text.addDisposeListener(new DisposeListener(){
@Override
public void widgetDisposed(final DisposeEvent e) {
SnippetEditorObservable.this.dispose();
}
});
}
/**
* Sets the bound {@link Text Text's} text to the passed <code>value</code>.
*
* @param value
* new value, String expected
* @see org.eclipse.core.databinding.observable.value.AbstractVetoableValue#doSetApprovedValue(java.lang.Object)
* @throws ClassCastException
* if the value is anything other than a String
*/
@Override
protected void doSetApprovedValue(final Object value) {
try {
this.updating= true;
this.text.setText(value == null ? "" : value.toString()); //$NON-NLS-1$
this.oldValue= this.text.getText();
} finally {
this.updating= false;
}
}
/**
* Returns the current value of the {@link Text}.
*
* @see org.eclipse.core.databinding.observable.value.AbstractVetoableValue#doGetValue()
*/
@Override
public Object doGetValue() {
return this.oldValue= this.text.getText();
}
/**
* Returns the type of the value from {@link #doGetValue()}, i.e.
* String.class
*
* @see org.eclipse.core.databinding.observable.value.IObservableValue#getValueType()
*/
@Override
public Object getValueType() {
return String.class;
}
@Override
public void dispose() {
if (!this.text.isDisposed()) {
if (this.updateEventType != SWT.None) {
this.text.removeListener(this.updateEventType, this.updateListener);
}
this.text.removeVerifyListener(this.verifyListener);
}
super.dispose();
}
}