/**
* 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);
}
}
}
}