package org.ovirt.engine.ui.common.presenter;
import java.util.ArrayList;
import java.util.List;
import org.ovirt.engine.ui.common.presenter.popup.DefaultConfirmationPopupPresenterWidget;
import org.ovirt.engine.ui.common.uicommon.ContextSensitiveHelpManager;
import org.ovirt.engine.ui.common.uicommon.model.DeferredModelCommandInvoker;
import org.ovirt.engine.ui.common.uicommon.model.ModelBoundPopupHandler;
import org.ovirt.engine.ui.common.uicommon.model.ModelBoundPopupResolver;
import org.ovirt.engine.ui.common.utils.WebUtils;
import org.ovirt.engine.ui.common.widget.HasEditorDriver;
import org.ovirt.engine.ui.common.widget.HasUiCommandClickHandlers;
import org.ovirt.engine.ui.frontend.communication.AsyncOperationCompleteEvent;
import org.ovirt.engine.ui.frontend.communication.AsyncOperationStartedEvent;
import org.ovirt.engine.ui.uicommonweb.HasCleanup;
import org.ovirt.engine.ui.uicommonweb.UICommand;
import org.ovirt.engine.ui.uicommonweb.help.HelpTag;
import org.ovirt.engine.ui.uicommonweb.models.ConfirmationModel;
import org.ovirt.engine.ui.uicommonweb.models.ListModel;
import org.ovirt.engine.ui.uicommonweb.models.Model;
import org.ovirt.engine.ui.uicompat.ObservableCollection;
import com.google.gwt.event.shared.EventBus;
import com.google.inject.Provider;
/**
* Base class for popup presenter widgets bound to a UiCommon Window model.
* <p>
* It is assumed that each popup presenter widget is bound as non-singleton.
*
* @param <T>
* Window model type.
* @param <V>
* View type.
*/
public abstract class AbstractModelBoundPopupPresenterWidget<T extends Model, V extends AbstractModelBoundPopupPresenterWidget.ViewDef<T>>
extends AbstractPopupPresenterWidget<V> implements ModelBoundPopupResolver<T> {
public interface ViewDef<T extends Model> extends AbstractPopupPresenterWidget.ViewDef, HasEditorDriver<T> {
void setTitle(String title);
void setMessage(String message);
void setItems(Iterable<?> items);
void setHashName(String name);
HasUiCommandClickHandlers addFooterButton(String label, String uniqueId, boolean isPrimary);
void setHelpCommand(UICommand command);
void removeButtons();
void startProgress(String progressMessage);
void stopProgress();
void focusInput();
void updateTabIndexes();
void init(T model);
}
private static final List<HasCleanup> popupResourcesToCleanup = new ArrayList<>();
private final ModelBoundPopupHandler<T> popupHandler;
private T model;
private DeferredModelCommandInvoker modelCommandInvoker;
private int asyncOperationCounter;
public AbstractModelBoundPopupPresenterWidget(EventBus eventBus, V view) {
super(eventBus, view);
this.popupHandler = new ModelBoundPopupHandler<>(this, eventBus);
}
public AbstractModelBoundPopupPresenterWidget(EventBus eventBus, V view,
Provider<DefaultConfirmationPopupPresenterWidget> defaultConfirmPopupProvider) {
this(eventBus, view);
this.popupHandler.setDefaultConfirmPopupProvider(defaultConfirmPopupProvider);
}
@Override
public String[] getWindowPropertyNames() {
return new String[] { "Window" }; //$NON-NLS-1$
}
@Override
public Model getWindowModel(T source, String propertyName) {
return source.getWindow();
}
@Override
public void clearWindowModel(T source, String propertyName) {
source.setWindow(null);
}
@Override
public String[] getConfirmWindowPropertyNames() {
return new String[] { "ConfirmWindow" }; //$NON-NLS-1$
}
@Override
public Model getConfirmWindowModel(T source, String propertyName) {
return source.getConfirmWindow();
}
@Override
public void clearConfirmWindowModel(T source, String propertyName) {
source.setConfirmWindow(null);
}
@Override
public AbstractModelBoundPopupPresenterWidget<? extends Model, ?> getModelPopup(T source,
UICommand lastExecutedCommand, Model windowModel) {
// No-op, override as necessary
return null;
}
@Override
public AbstractModelBoundPopupPresenterWidget<? extends ConfirmationModel, ?> getConfirmModelPopup(T source,
UICommand lastExecutedCommand) {
// No-op, override as necessary
return null;
}
/**
* Initialize the view from the given model.
*/
public void init(final T model) {
this.model = model;
getView().init(model);
// Set up async operation listeners to automatically display/hide progress bar
asyncOperationCounter = 0;
addRegisteredHandler(AsyncOperationStartedEvent.getType(), event -> {
if (event.getTarget() != getModel() || getModel().getProgress() != null) {
return;
}
if (asyncOperationCounter == 0) {
startProgress(null);
}
asyncOperationCounter++;
});
addRegisteredHandler(AsyncOperationCompleteEvent.getType(), event -> {
if (event.getTarget() != getModel() || getModel().getProgress() != null) {
return;
}
asyncOperationCounter--;
if (asyncOperationCounter == 0) {
stopProgress();
}
});
// Set up model command invoker
this.modelCommandInvoker = new DeferredModelCommandInvoker(model) {
@Override
protected void commandFailed(UICommand command) {
// Clear Window and ConfirmWindow models when "Cancel" command execution fails
if (command.getIsCancel() && command.getTarget() instanceof Model) {
Model source = (Model) command.getTarget();
source.setWindow(null);
source.setConfirmWindow(null);
}
}
@Override
protected void commandFinished(UICommand command) {
// Enforce popup close after executing "Cancel" command
if (command.getIsCancel()) {
hideAndUnbind();
}
}
};
// Set common popup properties
updateTitle(model);
updateMessage(model);
updateItems(model);
updateHashName(model);
updateHelpTag(model);
model.getPropertyChangedEvent().addListener((ev, sender, args) -> {
String propName = args.propertyName;
if ("Title".equals(propName)) { //$NON-NLS-1$
updateTitle(model);
} else if ("Message".equals(propName)) { //$NON-NLS-1$
updateMessage(model);
} else if ("Items".equals(propName)) { //$NON-NLS-1$
updateItems(model);
} else if ("HashName".equals(propName)) { //$NON-NLS-1$
updateHashName(model);
} else if ("HelpTag".equals(propName)) { //$NON-NLS-1$
updateHelpTag(model);
} else if ("OpenDocumentation".equals(propName)) { //$NON-NLS-1$
openDocumentation(model);
}
});
// Add popup footer buttons
addFooterButtons(model);
if (model.getCommands() instanceof ObservableCollection) {
ObservableCollection<UICommand> commands = (ObservableCollection<UICommand>) model.getCommands();
commands.getCollectionChangedEvent().addListener((ev, sender, args) -> {
getView().removeButtons();
addFooterButtons(model);
getView().updateTabIndexes();
});
}
// Register dialog model property change listener
popupHandler.addDialogModelListener(model);
// Initialize popup contents from the model
getView().edit(model);
getView().updateTabIndexes();
if (!model.hasEventBusSet()) {
model.setEventBus((EventBus) getEventBus());
}
}
@Override
protected void onClose() {
// Close button behaves as if the user pressed the Escape key
handleEscapeKey();
}
@Override
protected void handleEnterKey() {
getView().flush();
beforeCommandExecuted(model.getDefaultCommand());
modelCommandInvoker.invokeDefaultCommand();
}
@Override
protected void handleEscapeKey() {
getView().flush();
beforeCommandExecuted(model.getCancelCommand());
modelCommandInvoker.invokeCancelCommand();
}
@Override
protected boolean shouldDestroyOnClose() {
// We will call hideAndUnbind manually after executing "Cancel" command
return false;
}
@Override
public void hideAndUnbind() {
super.hideAndUnbind();
// Clear the model reference
model = null;
// Clean up all popup resources
if (getActivePopupCount() == 0) {
cleanupPopupResources();
}
}
void cleanupPopupResources() {
for (HasCleanup res : popupResourcesToCleanup) {
if (res != null) {
res.cleanup();
}
}
popupResourcesToCleanup.clear();
}
/**
* Callback right before any command is executed on the popup.
*/
protected void beforeCommandExecuted(UICommand command) {
// No-op, override as necessary
}
@Override
protected void onReveal() {
super.onReveal();
// Mark popup resources for future cleanup
popupResourcesToCleanup.add(getView());
popupResourcesToCleanup.add(model);
// Try to focus some popup input widget
getView().focusInput();
}
void updateTitle(T model) {
getView().setTitle(model.getTitle());
}
protected void updateMessage(T model) {
getView().setMessage(model.getMessage());
}
protected void updateHashName(T model) {
String hashName = model.getHashName();
getView().setHashName(hashName);
}
protected void updateHelpTag(T model) {
HelpTag helpTag = model.getHelpTag();
UICommand command = model.getOpenDocumentationCommand();
if (command != null && helpTag != null && ContextSensitiveHelpManager.getPath(helpTag.name()) != null) {
command.setIsAvailable(true);
getView().setHelpCommand(command); // also sets the help icon visible
}
}
void updateItems(T model) {
if (model instanceof ListModel) {
getView().setItems(((ListModel) model).getItems());
}
}
void addFooterButtons(T model) {
for (int i = model.getCommands().size() - 1; i >= 0; i--) {
UICommand command = model.getCommands().get(i);
final HasUiCommandClickHandlers button = getView().addFooterButton(
command.getTitle(), command.getName(),
model.getDefaultCommand() != null && model.getDefaultCommand().equals(command));
button.setCommand(command);
// Register command execution handler
registerHandler(button.addClickHandler(event -> {
getView().flush();
beforeCommandExecuted(button.getCommand());
button.getCommand().execute();
}));
}
}
/**
* Shows the popup progress indicator.
*/
public void startProgress(String progressMessage) {
getView().startProgress(progressMessage);
}
/**
* Hides the popup progress indicator.
*/
public void stopProgress() {
getView().stopProgress();
}
/**
* Open the context-sensitive help for this model. Opens in a new window.
* ContextSensitiveHelpManager locates the proper URL via helptag/csh mapping file.
*/
protected void openDocumentation(T model) {
String helpTag = model.getHelpTag().name();
String docPath = ContextSensitiveHelpManager.getPath(helpTag);
String docBase = model.getConfigurator().getDocsBaseUrl();
WebUtils.openUrlInNewWindow("_blank", docBase + docPath, WebUtils.OPTION_SCROLLBARS); //$NON-NLS-1$
}
public T getModel() {
return model;
}
}