asquare.gwt.tk.client.ui.AlertDialog.java Source code

Java tutorial

Introduction

Here is the source code for asquare.gwt.tk.client.ui.AlertDialog.java

Source

/*
 * Copyright 2006 Mat Gessel <mat.gessel@gmail.com>
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package asquare.gwt.tk.client.ui;

import java.util.List;

import asquare.gwt.tk.client.ui.behavior.*;
import asquare.gwt.tk.client.ui.resource.TkImageFactory;
import asquare.gwt.tk.client.util.DomUtil;
import asquare.gwt.tk.client.util.KeyMap;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.*;
import com.google.gwt.user.client.ui.*;

/**
 * A modal dialog tailored to conveniently displaying alerts. 
 * <p>Features: 
 * <dl>
 * <dt>Icon</dt>
 * <dd>An image indicating the type / severity of the condition. </dd>
 * <dt>Caption Text</dt>
 * <dd>A brief summary of the condition which triggered the dialog. Displayed in the "title". </dd>
 * <dt>Message</dt>
 * <dd>A detail of the consequences of the actions presented by the dialog.</dd>
 * <dt>Buttons</dt>
 * <dd>0 or more buttons. The button text should correspond to the alert message, as if the user
 * is answering a question. </dd>
 * <dt>Hot keys</dt>
 * <dd>Hot keys can be assigned to trigger actions when pressed. </dd>
 * <dt>Button roles</dt>
 * <dd>The "Default" button is automatically focused when the dialog is shown. 
 * The "Cancel" button maps to the "Esc" hotkey. </dd>
 * </dl>
 * <p>A callback is assigned to each button in the form of a {@link com.google.gwt.user.client.Command Command}. 
 * When a button is pressed, the dialog will be hidden and the command will be executed. 
 * <h3>CSS Style Rules</h3>
 * <ul class='css'>
 * <li>.tk-AlertDialog-defaultButton { the default button (if applicable) }</li>
 * <li>.tk-AlertDialog-captionLeft { the left part of the caption (contains the icon)}</li>
 * <li>.tk-AlertDialog-captionCenter { the center part of the caption (contains the caption text) }</li>
 * <li>.tk-AlertDialog-captionRight { the right part of the caption (set width to center caption text) }</li>
 * <li>.tk-AlertDialog-captionIcon { the caption icon itself }</li>
 * <li>.tk-AlertDialog-message { the message between the caption and the buttons }</li>
 * <li>.tk-AlertDialog-buttons { the panel containing the buttons }</li>
 * <li>.tk-AlertDialog-hotKeyChar { the hotkey character in the button text (availible in factory generated dialogs) }</li>
 * </ul>
 * 
 * @see <a
 *      href="http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGWindows/chapter_17_section_6.html">Apple
 *      Human Interface Guidlines</a>
 * @see <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwue/html/ch09f.asp">Windows User Interface Guidelines</a>
 */
public class AlertDialog extends ModalDialog {
    /**
     * Text for the "OK" button used in factory generated dialogs. Applies the
     * <code>.tk-AlertDialog-hotKeyChar</code> style to "O".
     */
    public static final String TEXT_OK = "<span class='tk-AlertDialog-hotKeyChar'>O</span>K";

    /**
     * Text for the "Cancel" button used in factory generated dialogs. Applies the
     * <code>.tk-AlertDialog-hotKeyChar</code> style to "C".
     */
    public static final String TEXT_CANCEL = "<span class='tk-AlertDialog-hotKeyChar'>C</span>ancel";

    /**
     * Indicates a button which has no special roles. 
     */
    public static final int BUTTON_PLAIN = 0;

    /**
     * Indicates that a button has the <em>Default</em> role. The
     * <em>Default</em> button will have initial focus. Never make an action 
     * default if it could have severe consequences such as data loss. The
     * dialog may only have one button of this type.
     */
    public static final int BUTTON_DEFAULT = 1 << 0;

    /**
     * Indicates that a button has the <em>Cancel</em> role. Pressing
     * <code>Esc</code> will execute the button's associated command. The
     * dialog may only have one button of this type.
     */
    public static final int BUTTON_CANCEL = 1 << 1;

    /**
     * Indicates that a button has both the <em>Default</em> and
     * <em>Cancel</em> roles. It will have initial focus and the user can press
     * <code>Esc</code> to execute this button's command. The dialog may
     * have no other buttons of type <code>Default</code> or
     * <code>Cancel</code>.
     */
    public static final int BUTTON_CANCEL_DEFAULT = BUTTON_DEFAULT | BUTTON_CANCEL;

    private final ColumnPanel m_buttonPanel = new ColumnPanel();
    private final KeyMap m_keyMap = new KeyMap();

    private Image m_captionIcon = null;
    private String m_captionText = null;
    private boolean m_captionTextAsHtml = false;
    private Widget m_message = null;
    private Widget m_defaultButton = null;

    /**
     * Creates an empty AlertDialog. 
     */
    public AlertDialog() {
        addStyleName("tk-AlertDialog");
    }

    /**
     * Creates a low severity modal dialog with an OK button. 
     * Use for hints, tips, welcome messages, etc.... 
     * 
     * @param okCommand a command to execute after the dialog is dismissed, or null
     * @param captionText a String to display in the dialog title, or null
     * @param message text a String display in the content area of the dialog, or null
     */
    public static AlertDialog createInfo(Command okCommand, String captionText, String message) {
        AlertDialog dialog = new AlertDialog();
        dialog.setCaptionText(captionText, false);
        dialog.setMessage(message);
        dialog.setIcon(TkImageFactory.getInstance().createAlertDialogImages().InfoIcon16().createImage());
        dialog.addButton(TEXT_OK, 'o', okCommand, BUTTON_DEFAULT | BUTTON_CANCEL);
        return dialog;
    }

    /**
     * Creates a medium severity modal dialog with a OK and Cancel buttons. 
     * Use for "Do you want to continue" style dialogs. 
     * 
     * @param okCommand a command to execute if the user presses the OK button, or null
     * @param captionText a String to display in the dialog title, or null
     * @param message text a String display in the content area of the dialog, or null
     */
    public static AlertDialog createWarning(Command okCommand, String captionText, String message) {
        AlertDialog dialog = new AlertDialog();
        dialog.setCaptionText(captionText, false);
        dialog.setMessage(message);
        dialog.setIcon(TkImageFactory.getInstance().createAlertDialogImages().AlertIcon16().createImage());
        dialog.addButton(TEXT_OK, 'o', okCommand, BUTTON_DEFAULT);
        dialog.addButton(TEXT_CANCEL, 'c', null, BUTTON_CANCEL);
        return dialog;
    }

    /**
     * Creates a high severity "stop" modal dialog with an OK button. Use when
     * an error condition prevents the normal execution of the application.
     * 
     * @param okCommand a command to execute after the dialog is dismissed, or null
     * @param captionText a String to display in the dialog title, or null
     * @param message text a String display in the content area of the dialog, or null
     */
    public static AlertDialog createError(Command okCommand, String captionText, String message) {
        AlertDialog dialog = new AlertDialog();
        dialog.setCaptionText(captionText, false);
        dialog.setMessage(message);
        dialog.setIcon(TkImageFactory.getInstance().createAlertDialogImages().ErrorIcon16().createImage());
        dialog.addButton(TEXT_OK, 'o', okCommand, BUTTON_DEFAULT | BUTTON_CANCEL);
        return dialog;
    }

    @Override
    protected List<Controller> createControllers() {
        List<Controller> result = super.createControllers();
        result.add(new HotKeyController());
        result.add(new ArrowKeyFocusController());
        return result;
    }

    /**
     * Gets the map of hotkeys to commands. Alpha keycodes are represented in upper
     * case. 
     * 
     * @return the keymap
     */
    public KeyMap getKeyMap() {
        return m_keyMap;
    }

    /**
     * Get the image that will be displayed in the caption. 
     */
    public Image getIcon() {
        return m_captionIcon;
    }

    /**
     * Set the image that will be displayed in the caption. 
     * 
     * @param url a URL to an image or null
     */
    public void setIcon(String url) {
        setIcon(url != null ? new Image(url) : null);
    }

    /**
     * Set the image will be displayed in the caption. You can use an
     * {@link Icon} to ensure size information is available when the dialog
     * layout is calculated.
     * 
     * @param icon an image or null
     * @see Icon
     */
    public void setIcon(Image icon) {
        if (m_captionIcon != null) {
            m_captionIcon = null;
        }
        if (icon != null) {
            m_captionIcon = icon;
            m_captionIcon.addStyleName("tk-AlertDialog-captionIcon");
            Image.prefetch(icon.getUrl());
        }
    }

    /**
     * Get the text which will be displayed in the caption. 
     * 
     * @return a String or null
     */
    public String getCaptionText() {
        return m_captionText;
    }

    /**
     * Set the text which will be displayed in the caption.
     * 
     * @param captionText a String or null
     * @param asHtml true to treat <code>captionText</code> as HTML, false to
     *            treat <code>captionText</code> as plain text
     */
    public void setCaptionText(String captionText, boolean asHtml) {
        m_captionText = captionText;
        m_captionTextAsHtml = asHtml;
    }

    /**
     * Not supported.
     * 
     * @throws UnsupportedOperationException
     * @deprecated
     */
    @Override
    public void setCaption(String text, boolean asHtml) {
        throw new UnsupportedOperationException();
    }

    /**
     * Not supported.
     *  
     * @throws UnsupportedOperationException
     * @deprecated
     */
    @Override
    public void setCaption(Widget w) {
        throw new UnsupportedOperationException();
    }

    /**
     * Factory method which creates the caption. Called just before the dialog is
     * shown. 
     * 
     * @return the caption, or <code>null</code>
     */
    protected Widget buildCaption() {
        ColumnPanel captionPanel = new ColumnPanel();
        captionPanel.setWidth("100%"); // necessary so that descendent TD can have 100% width in Opera
        captionPanel.addCell();
        captionPanel.setCellStyleName("tk-AlertDialog-captionLeft");
        captionPanel.addCell();
        captionPanel.setCellStyleName("tk-AlertDialog-captionCenter");
        captionPanel.addCell();
        captionPanel.setCellStyleName("tk-AlertDialog-captionRight");

        if (m_captionIcon != null) {
            captionPanel.addWidgetTo(m_captionIcon, 0);
        }

        if (m_captionText != null) {
            if (m_captionTextAsHtml) {
                DOM.setInnerHTML(captionPanel.getCellElement(1), m_captionText);
            } else {
                DOM.setInnerText(captionPanel.getCellElement(1), m_captionText);
            }
        }

        return captionPanel;
    }

    private void buildContent() {
        if (m_message != null) {
            m_message.addStyleName("tk-AlertDialog-message");
            add(m_message);
        }
        m_buttonPanel.setStyleName("tk-AlertDialog-buttons");
        DomUtil.setAttribute(m_buttonPanel, "cellSpacing", "");
        DomUtil.setAttribute(m_buttonPanel, "cellPadding", "");
        add(m_buttonPanel);
    }

    /**
     * Get the message which will be displayed in the dialog. 
     * 
     * @return a String, or <code>null</code>
     */
    public Widget getMessage() {
        return m_message;
    }

    /**
     * Set the message which will be displayed in the dialog. 
     * 
     * @param text a String, or <code>null</code>
     */
    public void setMessage(String text) {
        setMessage(text, false);
    }

    /**
     * Set the message which will be displayed in the dialog. 
     * 
     * @param text a String, or <code>null</code>
     * @param asHtml true to treat <code>captionText</code> as HTML, false to
     *            treat <code>captionText</code> as plain text
     */
    public void setMessage(String text, boolean asHtml) {
        if (text == null) {
            setMessage((Widget) null);
        } else {
            if (asHtml) {
                setMessage(new HTML(text));
            } else {
                setMessage(new Label(text));
            }
        }
    }

    /**
     * Set a widget to be displayed in the message area of the dialog. 
     * 
     * @param widget a Widget, or <code>null</code>
     */
    public void setMessage(Widget widget) {
        m_message = widget;
    }

    /**
     * Gets the widget in the button panel corresponding to <code>index</code>. 
     * 
     * @param index an integer >= 0
     * @return the button widget
     */
    public Widget getButton(int index) {
        return m_buttonPanel.getWidgetAt(index, 0);
    }

    /**
     * Gets the number of widgets in the button panel. 
     */
    public int getButtonCount() {
        return m_buttonPanel.getWidgetCount();
    }

    /**
     * Adds a button to button panel. When the button is clicked, the dialog
     * will be closed and the specified command will be executed.
     * 
     * @param text the text to display in the button
     * @param hotKey the keycode of a key which will execute the widget's
     *            associated command when pressed
     * @param command a command to execute if the button is clicked, or
     *            <code>null</code>
     * @param type a constant representing special button behavior
     */
    public void addButton(String text, char hotKey, Command command, int type) {
        addButton(new Button(text), hotKey, command, type);
    }

    /**
     * Adds a widget to button panel. The widget will be added to the focus
     * cycle if it implements {@link HasFocus} and does not have a tabIndex < 0.
     * The widget must implement {@link SourcesClickEvents}. When the widget is
     * clicked, the dialog will be closed and the specified command will be
     * executed.
     * 
     * @param widget the widget to add
     * @param hotKey the keycode of a key which will execute the widget's
     *            associated command when pressed
     * @param command a command to execute if the widget is clicked, or
     *            <code>null</code>
     * @param type a constant representing special button behavior
     * @throws ClassCastException if <code>widget</code> does not implement
     *             {@link SourcesClickEvents}
     */
    public void addButton(Widget widget, char hotKey, final Command command, int type) {
        addButton(widget, hotKey, type, true, command);
    }

    /**
     * Adds a widget to button panel. The widget will be added to the focus
     * cycle if it implements {@link HasFocus} and does not have a tabIndex < 0.
     * The widget must implement {@link SourcesClickEvents}. When the widget is
     * clicked, the specified command will be executed.
     * 
     * @param widget the widget to add
     * @param hotKey the keycode of a key which will execute the widget's
     *            associated command when pressed
     * @param type a constant representing special button behavior
     * @param closeDialog <code>true</code> to close the dialog when the
     *            button is clicked
     * @param command a command to execute if the widget is clicked, or
     *            <code>null</code>
     * @throws ClassCastException if <code>widget</code> does not implement
     *             {@link SourcesClickEvents}
     */
    public void addButton(Widget widget, char hotKey, int type, final boolean closeDialog, final Command command) {
        HasClickHandlers clickable = (HasClickHandlers) widget;
        boolean focusable = widget instanceof Focusable;
        final HideAndExecuteCommand command0 = new HideAndExecuteCommand(AlertDialog.this, closeDialog, command);

        clickable.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                command0.execute();
            }
        });
        m_buttonPanel.add(widget);
        if (focusable) {
            getFocusModel().add((Focusable) widget);
        }
        if ((type & BUTTON_DEFAULT) != 0) {
            widget.addStyleName("tk-AlertDialog-defaultButton");
            m_defaultButton = widget;
            if (focusable && getFocusModel().contains((Focusable) widget)) {
                getFocusModel().setFocusWidget((Focusable) widget);
            }
        }
        if ((type & BUTTON_CANCEL) != 0) {
            m_keyMap.put((char) KeyEvent.KEYCODE_ESCAPE, command0);
        }
        if (hotKey > 0) {
            m_keyMap.put(Character.toUpperCase(hotKey), command0);
        }
    }

    /**
     * Removes the specified button from the button panel.
     * <em>Note: this will not remove commands that were put in the keymap when the button was added. </em>
     * 
     * @param button
     * @see #getKeyMap()
     */
    public void removeButton(Widget button) {
        m_buttonPanel.remove(button);
        if (button == m_defaultButton) {
            m_defaultButton = null;
            m_defaultButton.removeStyleName("tk-AlertDialog-defaultButton");
        }
        if (button instanceof Focusable) {
            getFocusModel().remove(((Focusable) button));
        }
    }

    /*
     *  (non-Javadoc)
     * @see com.google.gwt.user.client.ui.PopupPanel#show()
     */
    @Override
    public void show() {
        show(null);
    }

    /*
     *  (non-Javadoc)
     * @see asquare.gwt.tk.client.ui.ModalDialog#show(com.google.gwt.user.client.ui.HasFocus)
     */
    @Override
    public void show(Focusable focusOnCloseWidget) {
        super.setCaption(buildCaption());
        buildContent();
        super.show(focusOnCloseWidget);
    }

    /**
     * A command wrapper which hides the dialog then executes a wrapped command.
     */
    public static class HideAndExecuteCommand implements Command {
        private final ModalDialog m_dialog;
        private final boolean m_hide;
        private final Command m_command;

        public HideAndExecuteCommand(ModalDialog dialog, Command command) {
            this(dialog, true, command);
        }

        public HideAndExecuteCommand(ModalDialog dialog, boolean hide, Command command) {
            m_dialog = dialog;
            m_hide = hide;
            m_command = command;
        }

        public void execute() {
            if (m_hide) {
                m_dialog.hide();
            }
            if (m_command != null) {
                DeferredCommand.addCommand(m_command);
            }
        }
    }

    /**
     * A controller which listens for the onkeydown event of a registered hotkey
     * and executes the associated command.
     */
    public static class HotKeyController extends ControllerAdaptor {
        public HotKeyController() {
            super(HotKeyController.class, Event.ONKEYDOWN);
        }

        @Override
        protected boolean doBrowserEvent(Widget widget, Event event) {
            final AlertDialog dialog = (AlertDialog) widget;
            char keyCode = (char) KeyEventImpl.getKeyCode(event);
            if (dialog.getKeyMap().containsKey(keyCode)) {
                dialog.getKeyMap().get(keyCode).execute();
                return false;
            }
            return true;
        }
    }

    /**
     * A controller which cycles the focus when the arrow keys are pressed.
     */
    public static class ArrowKeyFocusController extends ControllerAdaptor {
        public ArrowKeyFocusController() {
            super(ArrowKeyFocusController.class, Event.ONKEYDOWN);
        }

        @Override
        protected boolean doBrowserEvent(Widget widget, Event event) {
            boolean result = true;
            FocusModel focusModel = ((ModalDialog) widget).getFocusModel();
            if (focusModel != null && focusModel.getSize() > 1) {
                int keyCode = KeyEventImpl.getKeyCode(event);
                if (keyCode == KeyEvent.KEYCODE_RIGHT || keyCode == KeyEvent.KEYCODE_DOWN) {
                    // increment focus
                    focusModel.focusNextWidget();
                    result = false;
                } else if (keyCode == KeyEvent.KEYCODE_LEFT || keyCode == KeyEvent.KEYCODE_UP) {
                    // decrement focus
                    focusModel.focusPreviousWidget();
                    result = false;
                }
            }
            return result;
        }
    }
}