com.google.livingstories.client.ui.richtexttoolbar.RichTextToolbar.java Source code

Java tutorial

Introduction

Here is the source code for com.google.livingstories.client.ui.richtexttoolbar.RichTextToolbar.java

Source

/**
 * Copyright 2010 Google Inc.
 *
 * 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 com.google.livingstories.client.ui.richtexttoolbar;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.ImageBundle;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.PushButton;
import com.google.gwt.user.client.ui.RichTextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.ToggleButton;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.livingstories.client.ContentItemType;
import com.google.livingstories.client.BaseContentItem;
import com.google.livingstories.client.EventContentItem;
import com.google.livingstories.client.NarrativeContentItem;
import com.google.livingstories.client.lsp.SourceLink;
import com.google.livingstories.client.ui.SingleContentItemSelectionPanel;
import com.google.livingstories.client.util.RichTextUtil;

import java.util.EnumSet;
import java.util.Set;

/**
 * A toolbar for use with {@link RichTextArea}. It provides a simple UI
 * for all rich text formatting, dynamically displayed only for the available
 * functionality.
 * 
 * <p>A limited subset of the buttons will provide user feedback depending on
 * the cursor location.  Specifically, the following toggle buttons will appear
 * depressed if the cursor is over a portion of text that exhibits the
 * associated style:
 *
 * <ul>
 *   <li>BOLD_TOGGLE</li>
 *   <li>ITALIC_TOGGLE</li>
 *   <li>UNDERLINE_TOGGLE</li>
 *   <li>SUBSCRIPT_TOGGLE</li>
 *   <li>SUPERSCRIPT_TOGGLE</li>
 *   <li>STRIKETHROUGH_TOGGLE</li>
 * </ul>
 *
 *
 * <h3>CSS Style Rules</h3>
 * <ul class='css'>
 * <li>.gwt-RichTextToolbar { primary style }</li>
 * <li>.gwt-BoldToggle { on the BoldToggle control }</li>
 * <li>.gwt-ItalicToggle { on the ItalicToggle control }</li>
 * <li>.gwt-UnderlineToggle { on the UnderlineToggle control }</li>
 * <li>.gwt-SubscriptToggle { on the SubscriptToggle control }</li>
 * <li>.gwt-SuperscriptToggle { on the SuperscriptToggle control }</li>
 * <li>.gwt-JustifyLeftButton { on the JustifyLeftButton control }</li>
 * <li>.gwt-JustifyCenterButton { on the JustifyCenterButton control }</li>
 * <li>.gwt-JustifyRightButton { on the JustifyRightButton control }</li>
 * <li>.gwt-StrikethroughButton { on the StrikethroughButton control }</li>
 * <li>.gwt-IndentButton { on the IndentButton control }</li>
 * <li>.gwt-OutdentButton { on the OutdentButton control }</li>
 * <li>.gwt-HorizontalRuleButton { on the HorizontalRuleButton control }</li>
 * <li>.gwt-OrderedListButton { on the OrderedListButton control }</li>
 * <li>.gwt-UnorderedListButton { on the UnorderedListButton control }</li>
 * <li>.gwt-InsertImageButton { on the InsertImageButton control }</li>
 * <li>.gwt-CreateLinkButton { on the CreateLinkButton control }</li>
 * <li>.gwt-RemoveLinkButton { on the RemoveLinkButton control }</li>
 * <li>.gwt-RemoveFormatButton { on the RemoveFormatButton control }</li>
 * <li>.gwt-hasRichTextToolbar { added to associated RichTextArea }</li>
 * </ul>
 */
public class RichTextToolbar extends Composite {

    /** I18n messages. */
    private static final RichTextMessages MESSAGES = GWT.create(RichTextMessages.class);

    private static final RichTextArea.FontSize[] fontSizesConstants = new RichTextArea.FontSize[] {
            RichTextArea.FontSize.XX_SMALL, RichTextArea.FontSize.X_SMALL, RichTextArea.FontSize.SMALL,
            RichTextArea.FontSize.MEDIUM, RichTextArea.FontSize.LARGE, RichTextArea.FontSize.X_LARGE,
            RichTextArea.FontSize.XX_LARGE };

    private Images images = (Images) GWT.create(Images.class);
    private RichTextUtil richTextUtil = GWT.create(RichTextUtil.class);

    private RichTextArea richText;
    private RichTextArea.BasicFormatter basic;
    private RichTextArea.ExtendedFormatter extended;

    // All of these controls can be null, if the creator so chooses, so always
    // null-check them before attempting to access them
    private ToggleButton bold;
    private ToggleButton italic;
    private ToggleButton underline;
    private ToggleButton subscript;
    private ToggleButton superscript;
    private ToggleButton strikethrough;
    private PushButton indent;
    private PushButton outdent;
    private PushButton justifyLeft;
    private PushButton justifyCenter;
    private PushButton justifyRight;
    private PushButton hr;
    private PushButton ol;
    private PushButton ul;
    private PushButton insertImage;
    private PushButton insertGoToContentItem;
    private PushButton insertContentItem;
    private PushButton insertSource;
    private PushButton insertLightbox;
    private PushButton createLink;
    private PushButton removeLink;
    private PushButton removeFormat;
    private ListBox backColors;
    private ListBox foreColors;
    private ListBox fonts;
    private ListBox fontSizes;

    /**
     * Creates a new toolbar that drives the given rich text area, provides all
     * available controls, and divides the toolbar into two rows.
     * 
     * @param richText the {@link RichTextArea} to be controlled
     */
    public RichTextToolbar(RichTextArea richText) {
        this(richText, true);
    }

    /**
     * Creates a new toolbar that drives the given rich text area, provides the
     * given controls, and divides the toolbar into two rows.  If you prefer to
     * specify which controls are disabled (and keep the rest), then use
     * {@link EnumSet#complementOf} or the combination of
     * {@link EnumSet#allOf} and {@link EnumSet#remove}.
     * 
     * @param richText the {@link RichTextArea} to be controlled
     * @param controls the set of enabled controls
     */
    public RichTextToolbar(RichTextArea richText, Set<Control> controls) {
        this(richText, controls, true);
    }

    /**
     * Creates a new toolbar that drives the given rich text area, provides all
     * available controls, and optionally divides the toolbar into two rows.
     * 
     * @param richText the {@link RichTextArea} to be controlled
     * @param divideToolbar whether or not to divide the toolbar
     */
    public RichTextToolbar(RichTextArea richText, boolean divideToolbar) {
        this(richText, EnumSet.allOf(Control.class), divideToolbar);
    }

    /**
     * Creates a new toolbar that drives the given rich text area, provides the
     * given controls, and optionally divides the toolbar into two rows.  If you
     * prefer to specify which controls are disabled (and keep the rest), then
     * use {@link EnumSet#complementOf} or the combination of
     * {@link EnumSet#allOf} and {@link EnumSet#remove}.
     *
     * <p>The {@code divideToolbar} argument, if true, will divide the toolbar into
     * two sections - one for buttons, and one for the lists.  If false, both
     * sections will be on the same row.  With all buttons enabled, a single-row
     * toolbar can take upwards of 800 pixels of horizontal space.
     *
     * <p>If {@code controls} is null, then all available controls are enabled.
     * 
     * @param richText the {@link RichTextArea} to be controlled
     * @param controls the set of enabled controls
     * @param divideToolbar whether or not to divide the toolbar
     */
    public RichTextToolbar(RichTextArea richText, Set<Control> controls, boolean divideToolbar) {
        this.richText = richText;
        this.basic = richText.getBasicFormatter();
        this.extended = richText.getExtendedFormatter();

        boolean hasToggle = false;
        boolean hasTopPanel = false;
        boolean hasBottomPanel = false;

        HorizontalPanel topPanel = new HorizontalPanel();
        HorizontalPanel bottomPanel = null;
        VerticalPanel outer = null;

        if (divideToolbar) {
            bottomPanel = new HorizontalPanel();
            outer = new VerticalPanel();
            outer.add(topPanel);
            outer.add(bottomPanel);
        }

        if (basic != null) {
            if (controls.contains(Control.BOLD_TOGGLE)) {
                bold = createToggleButton(images.bold(), MESSAGES.bold());
                addControlToToolbar(topPanel, bold, "gwt-BoldToggle");
                hasTopPanel = true;
                hasToggle = true;
            }

            if (controls.contains(Control.ITALIC_TOGGLE)) {
                italic = createToggleButton(images.italic(), MESSAGES.italic());
                addControlToToolbar(topPanel, italic, "gwt-ItalicToggle");
                hasTopPanel = true;
                hasToggle = true;
            }

            if (controls.contains(Control.UNDERLINE_TOGGLE)) {
                underline = createToggleButton(images.underline(), MESSAGES.underline());
                addControlToToolbar(topPanel, underline, "gwt-UnderlineToggle");
                hasTopPanel = true;
                hasToggle = true;
            }

            if (controls.contains(Control.SUBSCRIPT_TOGGLE)) {
                subscript = createToggleButton(images.subscript(), MESSAGES.subscript());
                addControlToToolbar(topPanel, subscript, "gwt-SubscriptToggle");
                hasTopPanel = true;
                hasToggle = true;
            }

            if (controls.contains(Control.SUPERSCRIPT_TOGGLE)) {
                superscript = createToggleButton(images.superscript(), MESSAGES.superscript());
                addControlToToolbar(topPanel, superscript, "gwt-SuperscriptToggle");
                hasTopPanel = true;
                hasToggle = true;
            }

            if (controls.contains(Control.JUSTIFY_LEFT_BUTTON)) {
                justifyLeft = createPushButton(images.justifyLeft(), MESSAGES.justifyLeft());
                addControlToToolbar(topPanel, justifyLeft, "gwt-JustifyLeftButton");
                hasTopPanel = true;
            }

            if (controls.contains(Control.JUSTIFY_CENTER_BUTTON)) {
                justifyCenter = createPushButton(images.justifyCenter(), MESSAGES.justifyCenter());
                addControlToToolbar(topPanel, justifyCenter, "gwt-JustifyCenterButton");
                hasTopPanel = true;
            }

            if (controls.contains(Control.JUSTIFY_RIGHT_BUTTON)) {
                justifyRight = createPushButton(images.justifyRight(), MESSAGES.justifyRight());
                addControlToToolbar(topPanel, justifyRight, "gwt-JustifyRightButton");
                hasTopPanel = true;
            }
        }

        if (extended != null) {
            if (controls.contains(Control.STRIKETHROUGH_TOGGLE)) {
                strikethrough = createToggleButton(images.strikeThrough(), MESSAGES.strikeThrough());
                addControlToToolbar(topPanel, strikethrough, "gwt-StrikethroughButton");
                hasTopPanel = true;
                hasToggle = true;
            }

            if (controls.contains(Control.INDENT_BUTTON)) {
                indent = createPushButton(images.indent(), MESSAGES.indent());
                addControlToToolbar(topPanel, indent, "gwt-IndentButton");
                hasTopPanel = true;
            }

            if (controls.contains(Control.OUTDENT_BUTTON)) {
                outdent = createPushButton(images.outdent(), MESSAGES.outdent());
                addControlToToolbar(topPanel, outdent, "gwt-OutdentButton");
                hasTopPanel = true;
            }

            if (controls.contains(Control.HORIZONTAL_RULE_BUTTON)) {
                hr = createPushButton(images.hr(), MESSAGES.hr());
                addControlToToolbar(topPanel, hr, "gwt-HorizontalRuleButton");
                hasTopPanel = true;
            }

            if (controls.contains(Control.ORDERED_LIST_BUTTON)) {
                ol = createPushButton(images.ol(), MESSAGES.ol());
                addControlToToolbar(topPanel, ol, "gwt-OrderedListButton");
                hasTopPanel = true;
            }

            if (controls.contains(Control.UNORDERED_LIST_BUTTON)) {
                ul = createPushButton(images.ul(), MESSAGES.ul());
                addControlToToolbar(topPanel, ul, "gwt-UnorderedListButton");
                hasTopPanel = true;
            }

            if (controls.contains(Control.INSERT_IMAGE_BUTTON)) {
                insertImage = createPushButton(images.insertImage(), MESSAGES.insertImage());
                addControlToToolbar(topPanel, insertImage, "gwt-InsertImageButton");
                hasTopPanel = true;
            }

            if (controls.contains(Control.INSERT_GO_TO_CONTENT_ITEM_BUTTON)) {
                insertGoToContentItem = createPushButton(images.insertGoToContentItem(),
                        MESSAGES.insertGoToContentItem());
                addControlToToolbar(topPanel, insertGoToContentItem, "gwt-InsertGoToContentItemButton");
                hasTopPanel = true;
            }

            if (controls.contains(Control.INSERT_CONTENT_ITEM_BUTTON)) {
                insertContentItem = createPushButton(images.insertContentItem(), MESSAGES.insertContentItem());
                addControlToToolbar(topPanel, insertContentItem, "gwt-InsertContentItemButton");
                hasTopPanel = true;
            }

            if (controls.contains(Control.INSERT_SOURCE_BUTTON)) {
                insertSource = createPushButton(images.insertSource(), MESSAGES.insertSource());
                addControlToToolbar(topPanel, insertSource, "gwt-InsertSourceButton");
                hasTopPanel = true;
            }

            if (controls.contains(Control.INSERT_LIGHTBOX_BUTTON)) {
                insertLightbox = createPushButton(images.insertLightbox(), MESSAGES.insertLightbox());
                addControlToToolbar(topPanel, insertLightbox, "gwt-InsertLightboxButton");
                hasTopPanel = true;
            }

            if (controls.contains(Control.CREATE_LINK_BUTTON)) {
                createLink = createPushButton(images.createLink(), MESSAGES.createLink());
                addControlToToolbar(topPanel, createLink, "gwt-CreateLinkButton");
                hasTopPanel = true;
            }

            if (controls.contains(Control.REMOVE_LINK_BUTTON)) {
                removeLink = createPushButton(images.removeLink(), MESSAGES.removeLink());
                addControlToToolbar(topPanel, removeLink, "gwt-RemoveLinkButton");
                hasTopPanel = true;
            }

            if (controls.contains(Control.REMOVE_FORMAT_BUTTON)) {
                removeFormat = createPushButton(images.removeFormat(), MESSAGES.removeFormat());
                addControlToToolbar(topPanel, removeFormat, "gwt-RemoveFormatButton");
                hasTopPanel = true;
            }
        }

        if (basic != null) {
            HorizontalPanel panelToAddTo = bottomPanel != null ? bottomPanel : topPanel;
            if (controls.contains(Control.BACK_COLORS_LIST)) {
                backColors = createColorList(MESSAGES.background());
                addControlToToolbar(panelToAddTo, backColors, "gwt-BackColorsList");
                hasBottomPanel = true;
            }

            if (controls.contains(Control.FORE_COLORS_LIST)) {
                foreColors = createColorList(MESSAGES.foreground());
                addControlToToolbar(panelToAddTo, foreColors, "gwt-ForeColorsList");
                hasBottomPanel = true;
            }

            if (controls.contains(Control.FONTS_LIST)) {
                fonts = createFontList();
                addControlToToolbar(panelToAddTo, fonts, "gwt-FontsList");
                hasBottomPanel = true;
            }

            if (controls.contains(Control.FONT_SIZES_LIST)) {
                fontSizes = createFontSizes();
                addControlToToolbar(panelToAddTo, fontSizes, "gwt-FontSizesList");
                hasBottomPanel = true;
            }

            // We only use these listeners for updating status, so don't hook them up
            // unless at least basic editing is supported, and unless we have at
            // least one toggle button.
            if (hasToggle) {
                richText.addKeyUpHandler(new ToolbarKeyUpHandler());
                richText.addClickHandler(new ToolbarClickHandler());
            }
        }

        if (divideToolbar) {
            if (hasTopPanel && hasBottomPanel) {
                initWidget(outer);
            } else if (hasBottomPanel) {
                initWidget(bottomPanel);
            } else { // hasTopPanel (or has no panel)
                initWidget(topPanel);
            }
        } else { // divideToolbar == false
            // Also covers the case where they have no controls at all
            initWidget(topPanel);
        }

        setStyleName("gwt-RichTextToolbar");
        richText.addStyleName("gwt-hasRichTextToolbar");
    }

    private ListBox createColorList(String caption) {
        ListBox lb = new ListBox();
        lb.addChangeHandler(new ToolbarChangeHandler());
        lb.setVisibleItemCount(1);

        lb.addItem(caption);
        lb.addItem(MESSAGES.white(), "white");
        lb.addItem(MESSAGES.black(), "black");
        lb.addItem(MESSAGES.red(), "red");
        lb.addItem(MESSAGES.green(), "green");
        lb.addItem(MESSAGES.yellow(), "yellow");
        lb.addItem(MESSAGES.blue(), "blue");
        return lb;
    }

    private ListBox createFontList() {
        ListBox lb = new ListBox();
        lb.addChangeHandler(new ToolbarChangeHandler());
        lb.setVisibleItemCount(1);

        lb.addItem(MESSAGES.font(), "");
        lb.addItem(MESSAGES.normal(), "");
        lb.addItem(MESSAGES.timesNewRoman(), "Times New Roman");
        lb.addItem(MESSAGES.arial(), "Arial");
        lb.addItem(MESSAGES.courierNew(), "Courier New");
        lb.addItem(MESSAGES.georgia(), "Georgia");
        lb.addItem(MESSAGES.trebuchet(), "Trebuchet");
        lb.addItem(MESSAGES.verdana(), "Verdana");
        return lb;
    }

    private ListBox createFontSizes() {
        ListBox lb = new ListBox();
        lb.addChangeHandler(new ToolbarChangeHandler());
        lb.setVisibleItemCount(1);

        lb.addItem(MESSAGES.size());
        lb.addItem(MESSAGES.xxsmall());
        lb.addItem(MESSAGES.xsmall());
        lb.addItem(MESSAGES.small());
        lb.addItem(MESSAGES.medium());
        lb.addItem(MESSAGES.large());
        lb.addItem(MESSAGES.xlarge());
        lb.addItem(MESSAGES.xxlarge());
        return lb;
    }

    private PushButton createPushButton(AbstractImagePrototype img, String tip) {
        PushButton pb = new PushButton(img.createImage());
        pb.addClickHandler(new ToolbarClickHandler());
        pb.setTitle(tip);
        return pb;
    }

    private ToggleButton createToggleButton(AbstractImagePrototype img, String tip) {
        ToggleButton tb = new ToggleButton(img.createImage());
        tb.addClickHandler(new ToolbarClickHandler());
        tb.setTitle(tip);
        return tb;
    }

    private void addControlToToolbar(HorizontalPanel toolbar, Widget w, String styleName) {
        w.addStyleName(styleName);
        toolbar.add(w);
    }

    /**
     * Updates the status of all the stateful buttons.
     */
    private void updateStatus() {
        if (bold != null) {
            bold.setDown(basic.isBold());
        }

        if (italic != null) {
            italic.setDown(basic.isItalic());
        }

        if (underline != null) {
            underline.setDown(basic.isUnderlined());
        }

        if (subscript != null) {
            subscript.setDown(basic.isSubscript());
        }

        if (superscript != null) {
            superscript.setDown(basic.isSuperscript());
        }

        if (strikethrough != null) {
            strikethrough.setDown(extended.isStrikethrough());
        }
    }

    /**
     * The possible controls that are supported by this toolbar implementation.
     */
    public enum Control {
        BOLD_TOGGLE, ITALIC_TOGGLE, UNDERLINE_TOGGLE, SUBSCRIPT_TOGGLE, SUPERSCRIPT_TOGGLE, STRIKETHROUGH_TOGGLE, INDENT_BUTTON, OUTDENT_BUTTON, JUSTIFY_LEFT_BUTTON, JUSTIFY_CENTER_BUTTON, JUSTIFY_RIGHT_BUTTON, HORIZONTAL_RULE_BUTTON, ORDERED_LIST_BUTTON, UNORDERED_LIST_BUTTON, INSERT_IMAGE_BUTTON, INSERT_GO_TO_CONTENT_ITEM_BUTTON, INSERT_CONTENT_ITEM_BUTTON, INSERT_SOURCE_BUTTON, INSERT_LIGHTBOX_BUTTON, CREATE_LINK_BUTTON, REMOVE_LINK_BUTTON, REMOVE_FORMAT_BUTTON, BACK_COLORS_LIST, FORE_COLORS_LIST, FONTS_LIST, FONT_SIZES_LIST
    }

    private class ToolbarClickHandler implements ClickHandler {
        public void onClick(ClickEvent event) {
            Object sender = event.getSource();
            if (sender == bold) {
                basic.toggleBold();
            } else if (sender == italic) {
                basic.toggleItalic();
            } else if (sender == underline) {
                basic.toggleUnderline();
            } else if (sender == subscript) {
                basic.toggleSubscript();
            } else if (sender == superscript) {
                basic.toggleSuperscript();
            } else if (sender == strikethrough) {
                extended.toggleStrikethrough();
            } else if (sender == indent) {
                extended.rightIndent();
            } else if (sender == outdent) {
                extended.leftIndent();
            } else if (sender == justifyLeft) {
                basic.setJustification(RichTextArea.Justification.LEFT);
            } else if (sender == justifyCenter) {
                basic.setJustification(RichTextArea.Justification.CENTER);
            } else if (sender == justifyRight) {
                basic.setJustification(RichTextArea.Justification.RIGHT);
            } else if (sender == insertImage) {
                String url = Window.prompt(MESSAGES.enterImageUrl(), "http://");
                if (url != null) {
                    extended.insertImage(url);
                }
            } else if (sender == insertGoToContentItem) {
                PopupPanel popup = new PopupPanel();
                FlowPanel contentPanel = new FlowPanel();
                final SingleContentItemSelectionPanel selectionPanel = new SingleContentItemSelectionPanel();
                contentPanel.add(selectionPanel);
                contentPanel.add(createButtonPanel(popup, new ContentItemSelectionHandler() {
                    @Override
                    public void onSelect() {
                        BaseContentItem contentItem = selectionPanel.getSelection();
                        String headline = null;
                        if (contentItem.getContentItemType() == ContentItemType.EVENT) {
                            headline = ((EventContentItem) contentItem).getEventUpdate();
                        } else if (contentItem.getContentItemType() == ContentItemType.NARRATIVE) {
                            headline = ((NarrativeContentItem) contentItem).getHeadline();
                        } else {
                            Window.alert("You must link to an event or top level narrative!");
                            contentItem = null;
                        }
                        if (contentItem != null && contentItem.displayTopLevel()) {
                            richTextUtil.createJavascriptLink(richText.getElement(),
                                    "goToContentItem(" + contentItem.getId() + ")",
                                    "Jump to: " + headline.replace("\"", "'"));
                        }
                    }
                }));
                popup.add(contentPanel);
                popup.showRelativeTo(insertContentItem);
            } else if (sender == insertContentItem) {
                PopupPanel popup = new PopupPanel();
                FlowPanel contentPanel = new FlowPanel();
                final SingleContentItemSelectionPanel selectionPanel = new SingleContentItemSelectionPanel();
                contentPanel.add(selectionPanel);
                contentPanel.add(createButtonPanel(popup, new ContentItemSelectionHandler() {
                    @Override
                    public void onSelect() {
                        BaseContentItem contentItem = selectionPanel.getSelection();
                        if (contentItem != null) {
                            richTextUtil.createJavascriptLink(richText.getElement(),
                                    "showContentItemPopup(" + contentItem.getId() + ", this)");
                        }
                    }
                }));
                popup.add(contentPanel);
                popup.showRelativeTo(insertContentItem);
            } else if (sender == insertSource) {
                PopupPanel popup = new PopupPanel();
                FlowPanel contentPanel = new FlowPanel();
                final TextBox descriptionBox = new TextBox();
                HorizontalPanel descriptionPanel = new HorizontalPanel();
                descriptionPanel.add(new Label("Source description:"));
                descriptionPanel.add(descriptionBox);
                contentPanel.add(descriptionPanel);
                final SingleContentItemSelectionPanel selectionPanel = new SingleContentItemSelectionPanel();
                contentPanel.add(selectionPanel);
                contentPanel.add(createButtonPanel(popup, new ContentItemSelectionHandler() {
                    @Override
                    public void onSelect() {
                        String description = descriptionBox.getText();
                        BaseContentItem contentItem = selectionPanel.getSelection();
                        if (!description.isEmpty() || contentItem != null) {
                            String selectedText = richTextUtil.getSelection(richText.getElement());
                            richTextUtil.insertHTML(richText.getElement(), selectedText + " "
                                    + new SourceLink(description, contentItem == null ? -1 : contentItem.getId())
                                            .getOuterHTML());
                        }
                    }
                }));
                popup.add(contentPanel);
                popup.showRelativeTo(insertSource);
            } else if (sender == insertLightbox) {
                PopupPanel popup = new PopupPanel();
                FlowPanel contentPanel = new FlowPanel();
                final SingleContentItemSelectionPanel selectionPanel = new SingleContentItemSelectionPanel();
                contentPanel.add(selectionPanel);
                contentPanel.add(createButtonPanel(popup, new ContentItemSelectionHandler() {
                    @Override
                    public void onSelect() {
                        BaseContentItem contentItem = selectionPanel.getSelection();
                        if (contentItem != null) {
                            richTextUtil.createJavascriptLink(richText.getElement(), "showLightboxForContentItem('"
                                    + contentItem.getTypeString() + "', " + contentItem.getId() + ")");
                        }
                    }
                }));
                popup.add(contentPanel);
                popup.showRelativeTo(insertLightbox);
            } else if (sender == createLink) {
                String url = Window.prompt(MESSAGES.enterLinkUrl(), "http://");
                if (url != null) {
                    extended.createLink(url);
                }
            } else if (sender == removeLink) {
                extended.removeLink();
            } else if (sender == hr) {
                extended.insertHorizontalRule();
            } else if (sender == ol) {
                extended.insertOrderedList();
            } else if (sender == ul) {
                extended.insertUnorderedList();
            } else if (sender == removeFormat) {
                extended.removeFormat();
            } else if (sender == richText) {
                // We use the RichTextArea's onKeyUp event to update the toolbar status.
                // This will catch any cases where the user moves the cursur using the
                // keyboard, or uses one of the browser's built-in keyboard shortcuts.
                updateStatus();
            }
        }

        private Widget createButtonPanel(final PopupPanel popup, final ContentItemSelectionHandler handler) {
            Button okButton = new Button("Ok");
            okButton.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {
                    handler.onSelect();
                    popup.hide();
                }
            });
            Button cancelButton = new Button("Cancel");
            cancelButton.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {
                    popup.hide();
                }
            });

            HorizontalPanel buttons = new HorizontalPanel();
            buttons.add(okButton);
            buttons.add(cancelButton);
            return buttons;
        }
    }

    private class ToolbarChangeHandler implements ChangeHandler {
        public void onChange(ChangeEvent event) {
            Object sender = event.getSource();
            if (sender == backColors) {
                basic.setBackColor(backColors.getValue(backColors.getSelectedIndex()));
                backColors.setSelectedIndex(0);
            } else if (sender == foreColors) {
                basic.setForeColor(foreColors.getValue(foreColors.getSelectedIndex()));
                foreColors.setSelectedIndex(0);
            } else if (sender == fonts) {
                basic.setFontName(fonts.getValue(fonts.getSelectedIndex()));
                fonts.setSelectedIndex(0);
            } else if (sender == fontSizes) {
                basic.setFontSize(fontSizesConstants[fontSizes.getSelectedIndex() - 1]);
                fontSizes.setSelectedIndex(0);
            }
        }
    }

    private class ToolbarKeyUpHandler implements KeyUpHandler {
        public void onKeyUp(KeyUpEvent event) {
            if (event.getSource() == richText) {
                // We use the RichTextArea's onKeyUp event to update the toolbar status.
                // This will catch any cases where the user moves the cursur using the
                // keyboard, or uses one of the browser's built-in keyboard shortcuts.
                updateStatus();
            }
        }
    }

    private interface ContentItemSelectionHandler {
        void onSelect();
    }

    /**
     * This {@link ImageBundle} is used for all the button icons. Using an image
     * bundle allows all of these images to be packed into a single image, which
     * saves a lot of HTTP requests, drastically improving startup time.
     */
    public interface Images extends ImageBundle {
        AbstractImagePrototype bold();

        AbstractImagePrototype createLink();

        AbstractImagePrototype hr();

        AbstractImagePrototype indent();

        AbstractImagePrototype insertImage();

        AbstractImagePrototype insertGoToContentItem();

        AbstractImagePrototype insertContentItem();

        AbstractImagePrototype insertSource();

        AbstractImagePrototype insertLightbox();

        AbstractImagePrototype italic();

        AbstractImagePrototype justifyCenter();

        AbstractImagePrototype justifyLeft();

        AbstractImagePrototype justifyRight();

        AbstractImagePrototype ol();

        AbstractImagePrototype outdent();

        AbstractImagePrototype removeFormat();

        AbstractImagePrototype removeLink();

        AbstractImagePrototype strikeThrough();

        AbstractImagePrototype subscript();

        AbstractImagePrototype superscript();

        AbstractImagePrototype ul();

        AbstractImagePrototype underline();
    }

}