// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package com.google.appinventor.client.wizards;
import static com.google.appinventor.client.Ode.MESSAGES;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.DeckPanel;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
/**
* A wizard controls a dialog box that cycles through a series of pages.
* <p>
* The user may decide to cancel the wizard before viewing or completing
* all of the pages. In that case, the wizard is closed and no further
* action is taken.
* <p>
* After all pages have been cycled through, the user may invoke
* a finish command. Immediately before the command is invoked,
* the wizard dialog will be closed automatically.
*
*/
public abstract class Wizard extends DialogBox {
// UI for button panel to switch between wizard pages
private final HorizontalPanel buttonPanel;
private final Button backButton;
private final Button cancelButton;
private final Button nextButton;
private final Button okButton;
// Wizard pages
private final AbsolutePanel pagePanel;
private final DeckPanel pageDeck;
private int currentPageIndex;
// Command to execute upon finishing the wizard (not executed on cancel)
private Command finishCommand;
// Command to execute upon canceling the wizard (can be null)
private Command cancelCommand;
// Indicates whether the browser area size should be considered when calculating the wizard size
private final boolean adaptiveSizing;
// Indicates modality of the wizard
private final boolean modal;
/**
* Creates a new wizard.
* <p>
* Implementations are expected to build the wizard dialog in their
* constructor. In particular, it is expected that
* {@link #addPage(Panel)} and {@link #initFinishCommand(Command)}
* will be called before the constructor terminates.
*
* @param title title displayed in wizard dialog box
* @param modal indicates modality of the wizard
* @param adaptiveSizing instead of using the minimal size for the
* wizard also considers the size of the browser area
*/
protected Wizard(String title, boolean modal, boolean adaptiveSizing) {
// Initialize UI
// TODO(lizlooney) - investigate using built-in modality support. The
// reasons for not using it initially are no longer valid.
super(false, false);
this.modal = modal;
this.adaptiveSizing = adaptiveSizing;
setStylePrimaryName("ode-DialogBox");
setText(title);
ClickListener buttonListener = new ClickListener() {
@Override
public void onClick(Widget sender) {
if (sender == cancelButton) {
handleCancelClick();
} else if (sender == nextButton) {
showNextPage();
} else if (sender == backButton) {
showPreviousPage();
} else if (sender == okButton) {
handleOkClick();
}
}
};
cancelButton = new Button(MESSAGES.cancelButton());
cancelButton.addClickListener(buttonListener);
backButton = new Button(MESSAGES.backButton());
backButton.addClickListener(buttonListener);
nextButton = new Button(MESSAGES.nextButton());
nextButton.addClickListener(buttonListener);
okButton = new Button(MESSAGES.okButton());
okButton.addClickListener(buttonListener);
buttonPanel = new HorizontalPanel();
buttonPanel.add(cancelButton);
buttonPanel.add(backButton);
buttonPanel.add(nextButton);
buttonPanel.add(okButton);
buttonPanel.setSize("100%", "24px");
pageDeck = new DeckPanel();
pageDeck.setSize("100%", "100%");
pagePanel = new AbsolutePanel();
pagePanel.add(pageDeck);
pagePanel.setWidth("100%");
VerticalPanel contentPanel = new VerticalPanel();
contentPanel.add(pagePanel);
contentPanel.add(buttonPanel);
contentPanel.setSize("100%", "100%");
add(contentPanel);
}
@Override
public boolean onEventPreview(Event event) {
// Always allow event if capturing is enabled
if (DOM.getCaptureElement() != null) {
return true;
}
// If this is a modal wizard then only allow it if the target element is a child of this wizard
if (modal) {
Element target = DOM.eventGetTarget(event);
return (target != null && DOM.isOrHasChild(getElement(), target));
} else {
return super.onEventPreview(event);
}
}
/**
* {@inheritDoc}
*
* Subclasses may override this to perform additional actions
* after the wizard is shown, such as explicitly setting the
* initially focused widget. Remember to call {@code super.show()}
* as the first action in such overriding methods.
*/
@Override
public void show() {
ensureInited();
// Wizard size (having it resize between page changes is quite annoying)
int width = 480;
int height = 320;
if (adaptiveSizing) {
width = Math.max(width, Window.getClientWidth() / 3);
height = Math.max(height, Window.getClientHeight() / 2);
}
setPixelSize(width, height);
super.show();
if (pageDeck.getWidgetCount() == 1) {
buttonPanel.remove(backButton);
buttonPanel.remove(nextButton);
}
// Show first wizard page
currentPageIndex = 0;
showCurrentPage();
}
/**
* Adds a new page to the end of the wizard.
*/
protected void addPage(Panel page) {
page = new ScrollPanel(page);
pageDeck.add(page);
}
public void setPagePanelHeight(int height){
pagePanel.setHeight(height+"px");
}
/**
* Sets the command to be executed upon finish (not on cancel).
* May only be invoked once.
*/
protected void initFinishCommand(Command finishCommand) {
if (this.finishCommand != null) {
throw new IllegalStateException();
}
this.finishCommand = finishCommand;
}
/**
* Sets the command to be executed upon cancel.
* May be invoked no more than once.
*/
protected void initCancelCommand(Command cancelCommand) {
this.cancelCommand = cancelCommand;
}
/**
* Ensures that this class is fully initialized.
*/
private void ensureInited() {
if (pageDeck.getWidgetCount() == 0 || finishCommand == null) {
throw new IllegalStateException();
}
}
/**
* Invoked immediately after showing a new page
*
* @param pageNumber number of page to be shown
*/
protected void onPageInit(int pageNumber) {
}
/**
* Invoked immediately before moving away from the current page.
*
* @param pageNumber number of current page
*/
protected void onPageFinish(int pageNumber) {
}
/**
* Invoked immediately before closing the wizard.
*/
protected void onHide() {
}
/**
* Shows the previous page.
*/
protected void showPreviousPage() {
onPageFinish(currentPageIndex);
currentPageIndex--;
showCurrentPage();
onPageInit(currentPageIndex);
}
/**
* Shows the next page.
*/
protected void showNextPage() {
onPageFinish(currentPageIndex);
currentPageIndex++;
showCurrentPage();
onPageInit(currentPageIndex);
}
protected final void handleCancelClick() {
hideWizard();
if (cancelCommand != null) {
cancelCommand.execute();
}
}
protected final void handleOkClick() {
if (okButton.isEnabled()) {
hideWizard();
finishCommand.execute();
}
}
protected final Button getConfirmButton() {
return okButton;
}
protected void disableOkButton() {
okButton.setEnabled(false);
}
protected void enableOkButton() {
okButton.setEnabled(true);
}
/*
* Hides the wizard.
* Note that we are not overriding hide() because it is called by center() which can some
* ugliness!
*/
private void hideWizard() {
onPageFinish(currentPageIndex);
onHide();
hide();
}
/*
* Shows the wizard page for the currentPageIndex in the dialog box.
*/
private void showCurrentPage() {
// Enable back button if the current page is not the first page
backButton.setEnabled(currentPageIndex > 0);
// Enable next button if the current page is not the last page otherwise enable finish button
boolean isLastPage = currentPageIndex == pageDeck.getWidgetCount() - 1;
nextButton.setEnabled(!isLastPage);
okButton.setEnabled(isLastPage);
// Show page
pageDeck.showWidget(currentPageIndex);
// Because pages are embedded in scroll panels it is important to set the size of the page to
// the current height of the content panel
pageDeck.getWidget(currentPageIndex).setHeight(pagePanel.getOffsetHeight() + "px");
}
}