/******************************************************************************* * Copyright (c) 2008 * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the individual * copyright holders listed below, as Initial Contributors under such license. * The text of such license is available at * http://www.eclipse.org/legal/epl-v10.html. * * Contributors: * Henrik Lindberg *******************************************************************************/ package org.eclipse.equinox.p2.authoring.forms; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.operations.AbstractOperation; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.equinox.p2.authoring.forms.validators.IEditValidator; import org.eclipse.equinox.p2.authoring.forms.validators.NullValidator; import org.eclipse.equinox.p2.authoring.internal.IUndoOperationSupport; import org.eclipse.jface.dialogs.IMessageProvider; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.events.VerifyEvent; import org.eclipse.swt.events.VerifyListener; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.forms.editor.FormEditor; import org.eclipse.ui.forms.editor.IFormPage; import org.eclipse.ui.forms.widgets.FormToolkit; /** * An EditAdapter is used to hook a model object property via an {@link IMutator}, and an input validator in the form of * an {@link IEditValidator}. The EditAdapter needs access to an instance of {@link EditAdapterFormPart} to enable * setting and clearing of validation messages and handling of dirty state. * * An instance of EditAdapter is associated with a Control via the control's application data set with * {@link Control#setData(String, Object)} using the String "editAdapter" as the key. This enables widget oriented code * access to the EditAdapter. * * The EditAdapter supports {@link Text} and {@link Button} (check/radio) controls. * * @author Henrik Lindberg * */ public class EditAdapter implements ModifyListener, SelectionListener, VerifyListener { private boolean m_dirty; private boolean m_enabled; private boolean m_valid; private int m_refreshLock; private String m_currentStringValue; private String m_undoLabel; final private IEditValidator m_validator; final private Control m_control; final EditAdapterFormPart m_formPart; final IMutator m_mutator; /** * Create an edit adapter with a null validator (input is always valid), and a null mutator. * * @param control * @param formPart */ public EditAdapter(Control control, EditAdapterFormPart formPart) { this(control, formPart, NullValidator.instance(), new Mutator() { @Override public void setValue(String input) throws CoreException { // do nothing } @Override public String getValue() { return ""; //$NON-NLS-1$ } }); } /** * Creates and adds the adapter to the EditAdapterFormPart. * * @param key * the key used in the EditAdapterFormPart (for lookup and removal). * @param control * the control being adapted * @param formPart * the EditAdapterFormPart to add this adapter too * @param validator * a value validator * @param mutator * a mutator that updates the control and the model */ public EditAdapter(Control control, EditAdapterFormPart formPart, // IEditValidator validator, IMutator mutator) { m_dirty = false; m_enabled = true; m_valid = true; m_validator = validator; m_control = control; m_formPart = formPart; m_mutator = mutator; m_refreshLock = 0; m_undoLabel = "edit"; m_control.setData("editAdapter", this); if(m_control instanceof Text) { ((Text)m_control).addModifyListener(this); // ((Text)m_control).addVerifyListener(this); } else if(m_control instanceof Button) { ((Button)m_control).addSelectionListener(this); } else if(m_control instanceof StyledText) { ((StyledText)m_control).addModifyListener(this); } } public String getUndoLabel() { return m_undoLabel; } public void setUndoLabel(String undoLabel) { m_undoLabel = undoLabel; } /** * Tells the form part that dirty state changed. */ public void modifyText(ModifyEvent e) { // validate and set messages String input; if(m_control instanceof Text) input = ((Text)m_control).getText(); else if(m_control instanceof StyledText) input = ((StyledText)m_control).getText(); else throw new IllegalArgumentException("EditAdapter not attached to Text or StyledText"); // If the validator filters out characters, do that before input is validated // by setting the filtered text in the control. // String filtered = m_validator.inputFilter(input); if(filtered != null) { ((Text)m_control).setText(filtered); input = filtered; } m_valid = validate(input); if(m_refreshLock == 0) { FormToolkit toolkit = m_formPart.getManagedForm().getToolkit(); if(toolkit instanceof IUndoOperationSupport) { Object container = m_formPart.getManagedForm().getContainer(); Object memento = container instanceof IPageMementoProvider ? ((IPageMementoProvider)container).getPageMemento() : null; EditStringAdapterOperation op = new EditStringAdapterOperation(this,memento, m_currentStringValue, input); op.addContext(((IUndoOperationSupport)toolkit).getUndoContext()); ((IUndoOperationSupport)toolkit).getOperationHistory().add(op); } m_currentStringValue = input; setDirty(true); } } /** * Commit the data in the widget to the model. */ public void commit(boolean onSave) { if(m_control instanceof Text || m_control instanceof StyledText) commitText(onSave); else if(m_control instanceof Button) commitButton(onSave); } private void commitText(boolean onSave) { // validate and set messages String input; if(m_control instanceof Text) input = ((Text)m_control).getText(); else if(m_control instanceof StyledText) input = ((StyledText)m_control).getText(); else throw new IllegalArgumentException("EditAdapter not attached to Text or StyledText"); // If the validator filters out characters, do that before input is validated // by setting the filtered text in the control. // String filtered = m_validator.inputFilter(input); if(filtered != null) { ((Text)m_control).setText(filtered); input = filtered; } m_valid = validate(input); // update the model try { m_mutator.setValue(input); setDirty(false); // model is updated } catch(Exception e1) { // if the input was validated false by the validator - use the message // set by the validator, else use the message from the exception (this because // the validator should catch all errors, or there is something really wrong, so we // also print a stack trace. // if(m_valid) setErrorMessage(e1.getMessage()); m_valid = false; e1.printStackTrace(); // mark as dirty as we could not set the value setDirty(true); } } private void commitButton(boolean onSave) { // validate and set messages boolean input = ((Button)m_control).getSelection(); // update the model try { m_mutator.setValue(input); setDirty(false); // model is updated } catch(Exception e1) { // set message from exception as message - this should not really happen when setting a boolean. setErrorMessage(e1.getMessage()); m_valid = false; e1.printStackTrace(); // mark as dirty as we could not set the value setDirty(true); } } public boolean isEnabled() { return m_enabled; } public void setEnabled(boolean enabled) { m_enabled = enabled; } public boolean isValid() { return m_valid; } public void setValid(boolean valid) { m_valid = valid; } public IEditValidator getValidator() { return m_validator; } /** * Validates the input. Sets/Clear message as a side effect. * * @param input * @return true if input is valid, false otherwise */ public boolean validate(String input) { return m_validator.isValid(input, this); } /** * Sets a single error message keyed by this edit adapter. Replaces any earlier message set for the edit adapter. * * @param message */ public void setErrorMessage(String message) { m_formPart.getManagedForm().getMessageManager().addMessage(this, message, null, IMessageProvider.ERROR, m_control); } /** * Sets a single warning message keyed by this edit adapter. Replaces any earlier set message for the edit adapter. * * @param message */ public void setWarningMessage(String message) { m_formPart.getManagedForm().getMessageManager().addMessage(this, message, null, IMessageProvider.WARNING, m_control); } /** * Clears the message keyed by this edit adapter. Any previously set message will be cleared. */ public void clearMessages() { m_formPart.getManagedForm().getMessageManager().removeMessage(this, m_control); } /** * Refreshes the control's enablement state. */ public void refreshEnablement() { m_control.setEnabled(isEnabled()); } /** * Updates the control (if it is a Text control) with the value in the EditAdapter, and calls refresh enablement. */ public void refresh() { m_refreshLock++; try { if(m_control instanceof Text) { String input = m_mutator.getValue(); m_currentStringValue = input; ((Text)m_control).setText(input); // make sure the value is validated (i.e. that appropriate messages are set/cleared). validate(input); } else if(m_control instanceof StyledText) { String input = m_mutator.getValue(); m_currentStringValue = input; ((StyledText)m_control).setText(input); // make sure the value is validated (i.e. that appropriate messages are set/cleared). validate(input); } else if(m_control instanceof Button) { ((Button)m_control).setSelection(m_mutator.getBooleanValue()); } refreshEnablement(); setDirty(false); } finally { m_refreshLock--; } } private void setValue(String val, Object memento) { m_refreshLock++; try { if(m_control instanceof Text) { // Switch focus/context first (as this will change the text in some cases). switchFocus(memento); String input = val; m_currentStringValue = input; if(m_control instanceof Text) ((Text)m_control).setText(input); else if(m_control instanceof StyledText) ((StyledText)m_control).setText(input); // make sure the value is validated (i.e. that appropriate messages are set/cleared). validate(input); } refreshEnablement(); setDirty(true); } finally { m_refreshLock--; } } private void setValue(boolean val, Object memento) { m_refreshLock++; try { switchFocus(memento); if(m_control instanceof Button) { ((Button)m_control).setSelection(val); } refreshEnablement(); setDirty(true); } finally { m_refreshLock--; } } /** * Switches focus in the editor to the control's page, sets the page memento see {@link IPageMementoProvider} if the page * supports page memento, and then focuses the control. */ private void switchFocus(Object memento) { Object container = m_formPart.getManagedForm().getContainer(); if(container instanceof IFormPage) { IFormPage wantedPage = (IFormPage)container; FormEditor editor = wantedPage.getEditor(); IFormPage currentPage = editor.getActivePageInstance(); if(!wantedPage.getId().equals(currentPage.getId())) editor.setActivePage(wantedPage.getId()); if(wantedPage instanceof IPageMementoProvider) ((IPageMementoProvider)wantedPage).setPageMemento(memento); } m_control.setFocus(); } /** * Updates the control's enabled state based on the values in the control's attached edit adapter. Does nothing if * control does not have an attached edit adapter. * * @param control */ public static void refreshControl(Control control) { Object data = control.getData("editAdapter"); //$NON-NLS-1$ if(data instanceof EditAdapter) ((EditAdapter)data).refreshEnablement(); } /** * Sets the enabled of the control based on combination of normal and wanted state. This method is typically used * when enabling/disabling fields based on some grouping. When re-enabling fields in a disabled group, only fields * that were enabled before the group disablement should be disabled. * * @param control * @param enabled */ public static void enable(Control control, boolean enabled) { Object data = control.getData("editAdapter"); //$NON-NLS-1$ boolean normal = true; if(data instanceof EditAdapter) normal = ((EditAdapter)data).isEnabled(); boolean result = enabled && normal; control.setEnabled(result); } public boolean isDirty() { return m_dirty; } public void setDirty(boolean dirty) { m_dirty = dirty; if(m_dirty) m_formPart.markDirty(); } public Control getControl() { return m_control; } public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } public void widgetSelected(SelectionEvent e) { FormToolkit toolkit = m_formPart.getManagedForm().getToolkit(); boolean input = ((Button)m_control).getSelection(); if(toolkit instanceof IUndoOperationSupport) { Object container = m_formPart.getManagedForm().getContainer(); Object memento = container instanceof IPageMementoProvider ? ((IPageMementoProvider)container).getPageMemento() : null; EditBooleanAdapterOperation op = new EditBooleanAdapterOperation(this, memento, input); op.addContext(((IUndoOperationSupport)toolkit).getUndoContext()); // no need to execute this operation, the change has already taken place. ((IUndoOperationSupport)toolkit).getOperationHistory().add(op); } setDirty(true); } /** * Callback from widget (not curently used). */ public void verifyText(VerifyEvent e) { } public class EditStringAdapterOperation extends AbstractOperation { private String m_oldVal; private String m_newVal; private Object m_pageMemento; public EditStringAdapterOperation(EditAdapter editAdapter, Object pageMemento, String oldVal, String newVal) { super(editAdapter.getUndoLabel()); m_oldVal = oldVal; m_newVal = newVal; m_pageMemento = pageMemento; } @Override public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { // Nothing is needed here, as the change has already taken place if(monitor != null) monitor.done(); return Status.OK_STATUS; } @Override public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { setValue(m_newVal, m_pageMemento); if(monitor != null) monitor.done(); return Status.OK_STATUS; } @Override public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { setValue(m_oldVal, m_pageMemento); if(monitor != null) monitor.done(); return Status.OK_STATUS; } } public class EditBooleanAdapterOperation extends AbstractOperation { private boolean m_newVal; private Object m_pageMemento; public EditBooleanAdapterOperation(EditAdapter editAdapter, Object pageMemento, boolean newVal) { super(editAdapter.getUndoLabel()); m_newVal = newVal; m_pageMemento = pageMemento; } @Override public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { // Nothing is needed here, as the change has already taken place if(monitor != null) monitor.done(); return Status.OK_STATUS; } @Override public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { setValue(m_newVal, m_pageMemento); if(monitor != null) monitor.done(); return Status.OK_STATUS; } @Override public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { setValue(!m_newVal, m_pageMemento); if(monitor != null) monitor.done(); return Status.OK_STATUS; } } }