package edu.ualberta.med.biobank.forms; import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.Date; import java.util.Map; import org.eclipse.core.databinding.Binding; import org.eclipse.core.databinding.UpdateValueStrategy; import org.eclipse.core.databinding.observable.ChangeEvent; import org.eclipse.core.databinding.observable.IChangeListener; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.databinding.observable.value.WritableValue; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.action.Action; import org.eclipse.jface.dialogs.IMessageProvider; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.operation.IRunnableContext; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.viewers.ComboViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyAdapter; 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.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.ISaveablePart; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.contexts.IContextService; import org.eclipse.ui.forms.widgets.ScrolledForm; import org.eclipse.ui.services.ISourceProviderService; import edu.ualberta.med.biobank.SessionManager; import edu.ualberta.med.biobank.forms.input.FormInput; import edu.ualberta.med.biobank.gui.common.BgcPlugin; import edu.ualberta.med.biobank.gui.common.forms.BgcEntryFormActions; import edu.ualberta.med.biobank.gui.common.forms.BgcFormBase; import edu.ualberta.med.biobank.gui.common.forms.FieldInfo; import edu.ualberta.med.biobank.gui.common.forms.IBgcEntryForm; import edu.ualberta.med.biobank.gui.common.validators.AbstractValidator; import edu.ualberta.med.biobank.gui.common.widgets.BgcBaseText; import edu.ualberta.med.biobank.gui.common.widgets.DateTimeWidget; import edu.ualberta.med.biobank.gui.common.widgets.utils.ComboSelectionUpdate; import edu.ualberta.med.biobank.sourceproviders.ConfirmState; import edu.ualberta.med.biobank.treeview.AdapterBase; import edu.ualberta.med.biobank.widgets.BiobankLabelProvider; /** * Base class for data entry forms. * * Notes: - saveForm() is called in it's own thread so making calls to the * database is possible. * */ public abstract class BiobankEntryForm extends BiobankFormBase implements IBgcEntryForm { private static final String CONTEXT_ENTRY_FORM = "biobank.context.entryForm"; protected String sessionName; private boolean dirty = false; // The widget that is to get the focus when the form is created private Control firstControl; protected BgcEntryFormActions formActions; protected KeyListener keyListener = new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if ((e.keyCode & SWT.MODIFIER_MASK) == 0) { setDirty(true); } } }; protected ModifyListener modifyListener = new ModifyListener() { @Override public void modifyText(ModifyEvent e) { setDirty(true); } }; protected SelectionListener selectionListener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { setDirty(true); } }; public BiobankEntryForm() { super(); firstControl = null; widgetCreator.initDataBinding(); widgetCreator.setKeyListener(keyListener); widgetCreator.setModifyListener(modifyListener); widgetCreator.setSelectionListener(selectionListener); } public void formClosed() throws Exception { // TODO: is this necessary if making copies? if (adapter instanceof AdapterBase) if ((adapter != null) && (((AdapterBase) adapter).getModelObject() != null)) { ((AdapterBase) adapter).getModelObject().reload(); } // not everything is well initialized on the adapter before it is really // saved. Should not do that now.. // SessionManager.updateAdapterTreeNode(adapter); } @Override public void doSave(IProgressMonitor monitor) { setDirty(false); if (!formActions.getConfirmAction().isEnabled()) { monitor.setCanceled(true); setDirty(true); BgcPlugin.openAsyncError( "Form state", "Form in invalid state, save failed."); return; } doSaveInternal(monitor); } @SuppressWarnings("unused") protected void doSaveInternal(IProgressMonitor monitor) { IRunnableContext context = new ProgressMonitorDialog(Display .getDefault().getActiveShell()); try { doBeforeSave(); context.run(true, false, new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { try { monitor.beginTask("Saving...", IProgressMonitor.UNKNOWN); saveForm(); // this needs to be done there if we want the new node // to be in the tree and to be selected and to see the // right label (needs to be done when save is finished, // not when the form close) SessionManager.updateAllSimilarNodes(adapter, true); monitor.done(); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { try { doAfterSave(); } catch (Exception e) { setDirty(true); throw new RuntimeException(e); } } }); } catch (Exception ex) { saveErrorCatch(ex, monitor, true); } } }); } catch (Exception e) { setDirty(true); throw new RuntimeException(e); } } @Override protected void cancelSave(IProgressMonitor monitor) { Display.getDefault().syncExec(new Runnable() { @Override public void run() { setDirty(true); } }); super.cancelSave(monitor); } /** * Called before the monitor start. Can be used to get values on the GUI * objects. */ protected void doBeforeSave() throws Exception { // do nothing by default } /** * Invoked in GUI thread. * * Called after the monitor start. Can be used to get values on the GUI * objects. */ protected void doAfterSave() throws Exception { // default does nothing } @Override public void init(IEditorSite editorSite, IEditorInput input) throws PartInitException { super.init(editorSite, input); setDirty(false); } @Override public boolean isDirty() { return dirty; } protected void setDirty(boolean d) { dirty = d; firePropertyChange(ISaveablePart.PROP_DIRTY); } @Override public void createPartControl(Composite parent) { super.createPartControl(parent); addToolbarButtons(); bindChangeListener(); IContextService contextService = (IContextService) getSite() .getService(IContextService.class); contextService.activateContext(CONTEXT_ENTRY_FORM); } abstract protected void saveForm() throws Exception; @Override public void setFocus() { super.setFocus(); Assert.isNotNull(firstControl, "first control widget is not set"); if (!firstControl.isDisposed()) { firstControl.setFocus(); } } public String getSessionName() { return sessionName; } public void setSessionName(String sessionName) { this.sessionName = sessionName; } protected Control getFirstControl() { return firstControl; } public void setFirstControl(Control c) { firstControl = c; } public Binding addBooleanBinding(WritableValue writableValue, IObservableValue observableValue, final String errorMsg) { return addBooleanBinding(writableValue, observableValue, errorMsg, IStatus.ERROR); } public Binding addBooleanBinding(WritableValue writableValue, IObservableValue observableValue, String errorMsg, int statusType) { return widgetCreator.addBooleanBinding(writableValue, observableValue, errorMsg, statusType); } protected void createBoundWidgetsFromMap(Map<String, FieldInfo> fieldsMap, Object bean, Composite client) { widgetCreator.createBoundWidgetsFromMap(fieldsMap, bean, client); } protected Control createBoundWidgetWithLabel(Composite composite, Class<? extends Widget> widgetClass, int widgetOptions, String fieldLabel, String[] widgetValues, IObservableValue modelObservableValue, AbstractValidator validator) { return widgetCreator.createBoundWidgetWithLabel(composite, widgetClass, widgetOptions, fieldLabel, widgetValues, modelObservableValue, validator); } protected <T> ComboViewer createComboViewer(Composite parent, String fieldLabel, Collection<T> input, T selection, String errorMessage, ComboSelectionUpdate csu) { return widgetCreator.createComboViewer(parent, fieldLabel, input, selection, errorMessage, csu, new BiobankLabelProvider()); } protected DateTimeWidget createDateTimeWidget(Composite client, String nameLabel, Date date, IObservableValue modelObservableValue, AbstractValidator validator) { return widgetCreator.createDateTimeWidget(client, nameLabel, date, modelObservableValue, validator); } /* * Applies a background color to the read only field. */ @Override protected BgcBaseText createReadOnlyLabelledField(Composite parent, int widgetOptions, String fieldLabel, String value) { return createReadOnlyLabelledField(parent, widgetOptions, fieldLabel, value, true); } protected void bindChangeListener() { final IObservableValue statusObservable = new WritableValue(); statusObservable.addChangeListener(new IChangeListener() { @Override public void handleChange(ChangeEvent event) { IObservableValue validationStatus = (IObservableValue) event .getSource(); handleStatusChanged((IStatus) validationStatus.getValue()); } }); widgetCreator.addGlobalBindValue(statusObservable); } protected void bindValue(IObservableValue targetObservableValue, IObservableValue modelObservableValue, UpdateValueStrategy targetToModel, UpdateValueStrategy modelToTarget) { widgetCreator.bindValue(targetObservableValue, modelObservableValue, targetToModel, modelToTarget); } protected void handleStatusChanged(IStatus status) { if (status.getSeverity() == IStatus.OK) { setFormHeaderErrorMessage(getOkMessage(), IMessageProvider.NONE); setConfirmEnabled(true); } else { setFormHeaderErrorMessage(status.getMessage(), IMessageProvider.ERROR); setConfirmEnabled(false); } } protected void setConfirmEnabled(boolean enabled) { ISourceProviderService service = (ISourceProviderService) PlatformUI .getWorkbench().getActiveWorkbenchWindow() .getService(ISourceProviderService.class); ConfirmState confirmSourceProvider = (ConfirmState) service .getSourceProvider(ConfirmState.SESSION_STATE); if (confirmSourceProvider != null) { confirmSourceProvider.setState(enabled); Action confirmAction = formActions.getConfirmAction(); if (confirmAction != null) { confirmAction.setEnabled(enabled); } } form.getToolBarManager().update(true); } public void setFormHeaderErrorMessage(String message, int type) { if (!form.getForm().getHead().isDisposed()) { form.setMessage(message, type); } } /** * Called to get the string to display when the for is not in an error * state. * * @return the string to display at the top of the form. */ protected abstract String getOkMessage(); protected void addSeparator() { Label separator = toolkit.createSeparator(page, SWT.HORIZONTAL); GridData gd = new GridData(); gd.grabExcessHorizontalSpace = true; gd.horizontalAlignment = SWT.FILL; separator.setLayoutData(gd); } @Override public ScrolledForm getScrolledForm() { return form; } protected void addToolbarButtons() { formActions = new BgcEntryFormActions(this); addResetAction(); addCancelAction(); addConfirmAction(); form.updateToolBar(); } protected void addConfirmAction() { formActions.addConfirmAction(Actions.BIOBANK_CONFIRM); } protected void addResetAction() { formActions.addResetAction(Actions.BIOBANK_RELOAD); } protected void addCancelAction() { formActions.addCancelAction(Actions.BIOBANK_CANCEL); } protected void addPrintAction() { formActions.addPrintAction(); } protected void setEnablePrintAction(boolean enable) { formActions.setEnablePrintAction(enable); } @Override public boolean print() { // override me return false; } @Override public void confirm() { try { PlatformUI.getWorkbench().getActiveWorkbenchWindow() .getActivePage().saveEditor(this, false); if (!isDirty()) { closeEntryOpenView(true, openViewAfterSaving()); } } catch (Exception e) { LOGGER.error("Can't save the form", e); } } protected boolean openViewAfterSaving() { return true; } protected void closeEntryOpenView(boolean saveOnClose, boolean openView) { int entryIndex = linkedForms.indexOf(this); PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage() .closeEditor(this, saveOnClose); String nextFormId = getNextOpenedFormId(); if (openView && (nextFormId != null)) { AdapterBase.openForm(new FormInput(getAdapter()), nextFormId, true); int previousFormIndex = entryIndex - 1; if (previousFormIndex >= 0 && previousFormIndex < linkedForms.size()) { BgcFormBase form = linkedForms.get(previousFormIndex); IWorkbenchPage page = PlatformUI.getWorkbench() .getActiveWorkbenchWindow().getActivePage(); page.bringToTop(form); } } } @Override public void cancel() { try { boolean openView = adapter.getId() != null; if (adapter instanceof AdapterBase) openView &= !((AdapterBase) adapter).getModelObject().isNew(); closeEntryOpenView(true, openView); } catch (Exception e) { LOGGER.error("Can't cancel the form", e); } } /** * Return the ID of the form that should be opened after the save action is * performed and the current form closed */ public abstract String getNextOpenedFormId(); }