/*******************************************************************************
* Copyright (c) 2012 Pivotal Software, Inc.
* 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:
* Pivotal Software, Inc. - initial API and implementation
*******************************************************************************/
package org.springsource.ide.eclipse.commons.livexp.ui;
import java.util.List;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.forms.widgets.SharedScrolledComposite;
import org.eclipse.ui.progress.UIJob;
import org.springsource.ide.eclipse.commons.livexp.core.CompositeValidator;
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
import org.springsource.ide.eclipse.commons.livexp.core.ValueListener;
/**
* @author Kris De Volder
*/
public abstract class WizardPageWithSections extends WizardPage implements IPageWithSections, Reflowable, ValueListener<ValidationResult> {
/**
* A delay used for posting status messages to the dialog area after a status update happens.
* This is to get rid of spurious message that only appear for a fraction of a second as
* internal some auto updating states in models are inconsistent. E.g. in new boot project wizard
* when project name is entered it is temporarily inconsistent with default project location until
* that project location itself is update in response to the change event from the project name.
* If the project location validator runs before the location update, a spurious validation error
* temporarily results.
*
* Note: this is a hacky solution. It would be better if the LiveExp framework solved this by
* tracking and scheduling refreshes based on the depedency graph. Thus it might guarantee
* that the validator never sees the inconsistent state because it is refreshed last.
*/
private static final long MESSAGE_DELAY = 250;
protected WizardPageWithSections(String pageName, String title, ImageDescriptor titleImage) {
super(pageName, title, titleImage);
}
private List<WizardPageSection> sections = null;
private CompositeValidator validator;
private UIJob updateJob;
private SharedScrolledComposite scroller;
private Composite page;
private UIJob reflowJob;
/* (non-Javadoc)
* @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
*/
public void createControl(Composite parent) {
GridDataFactory.fillDefaults().grab(true,true).applyTo(parent);
scroller = new SharedScrolledComposite(parent, SWT.V_SCROLL | SWT.H_SCROLL) {};
// scroller.setWidthHint(500); // Avoid excessively wide dialogs
// Display display = Display.getCurrent();
// Color blue = display.getSystemColor(SWT.COLOR_BLUE);
// scroller.setBackground(blue);
scroller.setExpandHorizontal(true);
scroller.setExpandVertical(true);
page = new Composite(scroller, SWT.NONE);
GridLayout layout = new GridLayout(1, false);
layout.marginHeight = 12;
layout.marginWidth = 12;
page.setLayout(layout);
validator = new CompositeValidator();
for (PageSection section : getSections()) {
section.createContents(page);
validator.addChild(section.getValidator());
}
validator.addListener(this);
Dialog.applyDialogFont(page);
page.pack(true);
// scroller.setMinSize(page.getSize());
// scroller.setSize(page.getSize());
scroller.setContent(page);
setControl(scroller);
page.addPaintListener(new PaintListener() {
//This is a bit yuck. But don't know a better way to 'reflow' the page the first time
// it gets shown. And if we don't do that, then there are often layout issues.
//See for example: https://www.pivotaltracker.com/story/show/130209913
@Override
public void paintControl(PaintEvent e) {
page.removePaintListener(this);
reflow();
}
});
if (getContainer().getCurrentPage()!=null) { // Otherwise an NPE will ensue when updating buttons. Buttons depend on current page so that is logical.
getContainer().updateButtons();
getContainer().updateMessage();
}
}
@Override
public boolean reflow() {
if (reflowJob==null) {
reflowJob = new UIJob(Display.getDefault(), "Reflow Wizard Contents") {
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
if (scroller!=null && !scroller.isDisposed()) {
scroller.reflow(true);
}
return Status.OK_STATUS;
}
};
reflowJob.setSystem(true);
}
reflowJob.schedule();
return true;
}
protected synchronized List<WizardPageSection> getSections() {
if (sections==null) {
sections = createSections();
}
return sections;
}
/**
* This method should be implemented to generate the contents of the page.
*/
protected abstract List<WizardPageSection> createSections();
public void gotValue(LiveExpression<ValidationResult> exp, final ValidationResult status) {
// setPageComplete(status.isOk()); //Don't delay this, never allow clicking finish button if state not consistent.
scheduleUpdateJob();
}
private synchronized void scheduleUpdateJob() {
Shell shell = getShell();
if (shell!=null) {
if (this.updateJob==null) {
this.updateJob = new UIJob("Update Wizard message") {
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
ValidationResult status = validator.getValue();
setErrorMessage(null);
setMessage(null);
if (status.isOk()) {
} else if (status.status == IStatus.ERROR) {
setErrorMessage(status.msg);
} else if (status.status == IStatus.WARNING) {
setMessage(status.msg, IMessageProvider.WARNING);
} else if (status.status == IStatus.INFO) {
setMessage(status.msg, IMessageProvider.INFORMATION);
} else {
setMessage(status.msg, IMessageProvider.NONE);
}
setPageComplete(status.isOk());
return Status.OK_STATUS;
}
};
updateJob.setSystem(true);
}
updateJob.schedule(MESSAGE_DELAY);
}
}
public void dispose() {
for (WizardPageSection s : sections) {
s.dispose();
}
}
@Override
public IRunnableContext getRunnableContext() {
return getContainer();
}
}