/****************************************************************************** * Copyright (c) 2016 Oracle * 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: * Konstantin Komissarchik - initial implementation and ongoing maintenance ******************************************************************************/ package org.eclipse.sapphire.ui.forms.swt; import static org.eclipse.sapphire.ui.forms.swt.GridLayoutUtil.gdfill; import static org.eclipse.sapphire.ui.forms.swt.GridLayoutUtil.glayout; import java.util.Collections; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.sapphire.Element; import org.eclipse.sapphire.ElementType; import org.eclipse.sapphire.ExecutableElement; import org.eclipse.sapphire.FilteredListener; import org.eclipse.sapphire.modeling.ProgressMonitor; import org.eclipse.sapphire.modeling.Status; import org.eclipse.sapphire.ui.DelayedTasksExecutor; import org.eclipse.sapphire.ui.PartValidationEvent; import org.eclipse.sapphire.ui.Presentation; import org.eclipse.sapphire.ui.def.DefinitionLoader; import org.eclipse.sapphire.ui.forms.DialogDef; import org.eclipse.sapphire.ui.forms.DialogPart; import org.eclipse.sapphire.ui.forms.swt.internal.StatusDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Shell; /** * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a> */ public class SapphireDialog extends Dialog { private Element element; private boolean elementInstantiatedLocally; private DefinitionLoader.Reference<DialogDef> definition; private DialogPart part; private Button okButton; /** * Constructs a new SapphireDialog instance. Use the open method to actually open the dialog. * * <p>The model will be instantiated when the dialog is constructed and disposed when the dialog is closed. To avoid * resource leaks, the open method must be called if this constructor is used.</p> * * @param shell the shell * @param type the root model element type * @param definition the dialog definition */ public SapphireDialog( final Shell shell, final ElementType type, final DefinitionLoader.Reference<DialogDef> definition ) { super( shell ); if( type == null ) { throw new IllegalArgumentException(); } this.elementInstantiatedLocally = true; init( type.instantiate(), definition ); } /** * Constructs a new SapphireDialog instance. Use the open method to actually open the dialog. * * @param shell the shell * @param element the root model element * @param definition the dialog definition */ public SapphireDialog( final Shell shell, final Element element, final DefinitionLoader.Reference<DialogDef> definition ) { super( shell ); init( element, definition ); } /** * Initializes the dialog. This method is called from the constructors. It can be overridden by extenders. * * @param element the root model element * @param definition the dialog definition */ protected void init( final Element element, final DefinitionLoader.Reference<DialogDef> definition ) { if( element == null ) { throw new IllegalArgumentException(); } if( definition == null ) { throw new IllegalArgumentException(); } this.element = element; this.definition = definition; this.part = new DialogPart(); this.part.init( null, this.element, this.definition.resolve(), Collections.<String,String>emptyMap() ); this.part.initialize(); } public final Element element() { return this.element; } public final DialogDef definition() { return this.definition.resolve(); } @Override protected Control createDialogArea( final Composite parent ) { final Shell shell = getShell(); shell.setText( this.part.getLabel() ); final Composite composite = (Composite) super.createDialogArea( parent ); final Composite innerComposite = new Composite( composite, SWT.NONE ); innerComposite.setLayout( glayout( 2, 0, 0 ) ); innerComposite.setLayoutData( gdfill() ); final Presentation presentation = this.part.createPresentation( null, innerComposite ); presentation.render(); final String initialFocusProperty = this.part.definition().getInitialFocus().content(); if( initialFocusProperty != null ) { this.part.setFocus( initialFocusProperty ); } shell.addDisposeListener ( new DisposeListener() { public void widgetDisposed( final DisposeEvent event ) { presentation.dispose(); SapphireDialog.this.element = null; SapphireDialog.this.part.dispose(); SapphireDialog.this.part = null; SapphireDialog.this.definition.dispose(); SapphireDialog.this.definition = null; } } ); return composite; } @Override protected Control createContents( final Composite parent ) { final Composite composite = (Composite) super.createContents( parent ); composite.setBackground( this.part.getSwtResourceCache().color( this.part.getBackgroundColor() ) ); composite.setBackgroundMode( SWT.INHERIT_DEFAULT ); return composite; } @Override protected Control createButtonBar( final Composite parent ) { final Composite composite = (Composite) super.createButtonBar( parent ); this.okButton = getButton( IDialogConstants.OK_ID ); this.part.attach ( new FilteredListener<PartValidationEvent>() { @Override protected void handleTypedEvent( PartValidationEvent event ) { updateOkButtonEnablement(); } } ); updateOkButtonEnablement(); return composite; } @Override protected boolean isResizable() { return true; } @Override protected final void okPressed() { DelayedTasksExecutor.sweep(); if( this.part.validation().severity() == Status.Severity.ERROR ) { return; } if( performOkOperation() ) { super.okPressed(); } } /** * Performs tasks that need to run when user closes the dialog by pressing on the ok button. The default implementation * invokes the execute method on the associated model element if this element is an ExecutableElement. * * @return true if the dialog can be dismissed or false if an issue was encountered that requires user's attention */ protected boolean performOkOperation() { if( this.element instanceof ExecutableElement ) { final Status status = ( (ExecutableElement) this.element ).execute( new ProgressMonitor() ); if( status.severity() == Status.Severity.ERROR ) { return handleExecuteFailure( status ); } } return true; } /** * Called when the execute operation fails with an error status. This is only applicable if the associated model element * is an ExecutableElement. The default implementation opens a dialog showing the failure message and leaves the dialog open. * * @param status the failure status * @return true, if the dialog should be closed; false, otherwise */ protected boolean handleExecuteFailure( final Status status ) { StatusDialog.open( getShell(), status ); return false; } private void updateOkButtonEnablement() { if( ! this.okButton.isDisposed() ) { final boolean expected = ( this.part.validation().severity() != Status.Severity.ERROR ); final boolean actual = this.okButton.isEnabled(); if( expected != actual ) { this.okButton.setEnabled( expected ); } } } @Override public int open() { final int code = super.open(); if( this.elementInstantiatedLocally ) { this.element.dispose(); this.element = null; } return code; } }