/** * Copyright (c) Red Hat, Inc., contributors and others 2013 - 2014. All rights reserved * * Licensed under the Eclipse Public License version 1.0, available at * http://www.eclipse.org/legal/epl-v10.html */ package org.jboss.tools.forge.ui.internal.ext.wizards; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.eclipse.core.runtime.Platform; import org.eclipse.jface.dialogs.DialogPage; import org.eclipse.jface.fieldassist.ControlDecoration; import org.eclipse.jface.fieldassist.FieldDecoration; import org.eclipse.jface.fieldassist.FieldDecorationRegistry; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.jboss.forge.addon.ui.controller.CommandController; import org.jboss.forge.addon.ui.controller.WizardCommandController; import org.jboss.forge.addon.ui.input.InputComponent; import org.jboss.forge.addon.ui.metadata.UICommandMetadata; import org.jboss.forge.addon.ui.output.UIMessage; import org.jboss.forge.addon.ui.util.InputComponents; import org.jboss.tools.forge.ui.internal.ForgeUIPlugin; import org.jboss.tools.forge.ui.internal.ext.control.ControlBuilder; import org.jboss.tools.forge.ui.internal.ext.control.ControlBuilderRegistry; import org.jboss.tools.forge.ui.notifications.NotificationType; /** * A Forge Wizard Page * * @author <a href="mailto:ggastald@redhat.com">George Gastaldi</a> * */ public class ForgeWizardPage extends WizardPage implements Listener { private static final int VALIDATION_DELAY = 150; private CommandController controller; private boolean changed; private volatile boolean userTyping; private Thread validationThread = null; private List<ComponentControlEntry> componentControlEntries = new ArrayList<>(); public ForgeWizardPage(ForgeWizard wizard, CommandController controller) { super(controller.getMetadata().getName()); setWizard(wizard); setPageComplete(false); this.controller = controller; UICommandMetadata id = controller.getMetadata(); setTitle(id.getName()); setDescription(id.getDescription()); setImageDescriptor(ForgeUIPlugin.getForgeLogo()); } public CommandController getController() { return controller; } @Override public ForgeWizard getWizard() { return (ForgeWizard) super.getWizard(); } @Override public void createControl(Composite parent) { try { controller.initialize(); } catch (Exception e) { ForgeUIPlugin.log(e); ForgeUIPlugin.displayMessage("Error has occurred!", "See Error Log for details", NotificationType.ERROR); return; } Map<String, InputComponent<?, ?>> inputs = controller.getInputs(); Composite container = new Composite(parent, SWT.NULL); GridLayout layout = new GridLayout(); container.setLayout(layout); layout.numColumns = 3; // Init component control array for (Entry<String, InputComponent<?, ?>> entry : inputs.entrySet()) { String inputName = entry.getKey(); InputComponent<?, ?> input = entry.getValue(); ControlBuilder<Control> controlBuilder = ControlBuilderRegistry .getBuilderFor(input); Control control = controlBuilder.build(this, input, inputName, container); if (input.isRequired()) { decorateRequiredField(input, control); } Control[] modifiableControls = controlBuilder .getModifiableControlsFor(control); for (Control child : modifiableControls) { registerListeners(child); } controlBuilder.setupNote(container, control, input); componentControlEntries.add(new ComponentControlEntry(input, controlBuilder, control)); } initValidatePage(); setControl(container); } private void clearMessages() { setErrorMessage(null); setMessage(null); } private void registerListeners(Control control) { // Update page status control.addListener(SWT.Modify, this); control.addListener(SWT.DefaultSelection, this); control.addListener(SWT.Selection, this); // if a page is changed, subsequent pages should be invalidated ChangeListener cl = new ChangeListener(); control.addListener(SWT.Modify, cl); control.addListener(SWT.Selection, cl); } /** * Decorate required field */ private void decorateRequiredField(final InputComponent<?, ?> input, Control control) { FieldDecoration completerIndicator = FieldDecorationRegistry .getDefault().getFieldDecoration( FieldDecorationRegistry.DEC_REQUIRED); ControlDecoration dec = new ControlDecoration(control, SWT.LEFT | SWT.CENTER); dec.setImage(completerIndicator.getImage()); dec.setDescriptionText(completerIndicator.getDescription()); } /** * The <code>ForgeWizardPage</code> implementation of this * <code>Listener</code> method handles all events and enablements for * controls on this page. */ @Override public void handleEvent(final Event event) { if (isCurrentPage()) { userTyping = true; if (validationThread == null) { setPageComplete(false); getContainer().updateButtons(); validationThread = new Thread(new Runnable() { @Override public void run() { while (userTyping) { try { userTyping = false; Thread.sleep(VALIDATION_DELAY); } catch (InterruptedException e) { ForgeUIPlugin.log(e); return; } } Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (isCurrentPage()) { validationThread = null; setPageComplete(validatePage()); getContainer().updateButtons(); } } }); } }); validationThread.start(); event.doit = true; } } } private void updatePageState() { // Change enabled state if (componentControlEntries != null) { for (ComponentControlEntry entry : componentControlEntries) { InputComponent<?, ?> component = entry.getComponent(); ControlBuilder<Control> controlBuilder = entry .getControlBuilder(); Control control = entry.getControl(); controlBuilder.updateState(control, component); } } } /** * Called when the page is opened for the first time. * * It should display error messages unless the error message indicates a * required field */ private void initValidatePage() { updatePageState(); for (ComponentControlEntry entry : componentControlEntries) { if (InputComponents.validateRequired(entry.getComponent()) != null) { setPageComplete(false); return; } } setPageComplete(validatePage()); } /** * Returns whether this page's controls currently all contain valid values. * It also calls {@link ForgeWizardPage#setErrorMessage(String)} if an error * is found * * @return <code>true</code> if all controls are valid, and * <code>false</code> if at least one is invalid */ private boolean validatePage() { clearMessages(); updatePageState(); for (UIMessage message : controller.validate()) { switch (message.getSeverity()) { case ERROR: setErrorMessage(message.getDescription()); return false; case WARN: setWarningMessage(message.getDescription()); return true; case INFO: setInfoMessage(message.getDescription()); return true; } } return true; } public void setInfoMessage(String warningMessage) { setMessage(warningMessage, DialogPage.INFORMATION); if (isCurrentPage()) { getContainer().updateMessage(); } } public void setWarningMessage(String warningMessage) { setMessage(warningMessage, DialogPage.WARNING); if (isCurrentPage()) { getContainer().updateMessage(); } } @Override public void dispose() { super.dispose(); componentControlEntries.clear(); } @Override public boolean canFlipToNextPage() { boolean result; if (isWizard()) { result = ((WizardCommandController) controller).canMoveToNextStep(); } else { // Single page wizards cannot move to next step result = false; } return isPageComplete() && result; } private boolean isWizard() { return controller instanceof WizardCommandController; } public void setChanged(boolean changed) { this.changed = changed; } public boolean isChanged() { return changed; } @Override public void setVisible(boolean visible) { super.setVisible(visible); if (visible && Platform.OS_LINUX.equals(Platform.getOS())) { getContainer().getShell().setVisible(true); getContainer().getShell().forceActive(); } } /** * Stores a component/control relationship */ class ComponentControlEntry { private InputComponent<?, ?> component; private ControlBuilder<Control> controlBuilder; private Control control; public ComponentControlEntry(InputComponent<?, ?> component, ControlBuilder<Control> controlBuilder, Control control) { this.component = component; this.controlBuilder = controlBuilder; this.control = control; } public ControlBuilder<Control> getControlBuilder() { return controlBuilder; } public InputComponent<?, ?> getComponent() { return component; } public Control getControl() { return control; } } private class ChangeListener implements Listener { @Override public void handleEvent(Event evt) { if (isCurrentPage()) { setChanged(true); } } } }