org.xwiki.gwt.wysiwyg.client.plugin.style.StylePlugin.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.gwt.wysiwyg.client.plugin.style.StylePlugin.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.gwt.wysiwyg.client.plugin.style;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.xwiki.gwt.user.client.Config;
import org.xwiki.gwt.user.client.ui.rta.RichTextArea;
import org.xwiki.gwt.user.client.ui.rta.cmd.Command;
import org.xwiki.gwt.wysiwyg.client.Strings;
import org.xwiki.gwt.wysiwyg.client.plugin.internal.AbstractStatefulPlugin;
import org.xwiki.gwt.wysiwyg.client.plugin.internal.FocusWidgetUIExtension;
import org.xwiki.gwt.wysiwyg.client.plugin.style.exec.BlockStyleNameExecutable;
import org.xwiki.gwt.wysiwyg.client.plugin.style.exec.InlineStyleNameExecutable;

import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.OptGroupElement;
import com.google.gwt.dom.client.OptionElement;
import com.google.gwt.dom.client.SelectElement;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.user.client.ui.ListBox;

/**
 * Enhances the editor with the ability to apply predefined styles to the current text selection. Installs a list box on
 * the tool bar with the available styles.
 * 
 * @version $Id: 70f38bd300c4383dd6c7738e51309842ed4e3f48 $
 */
public class StylePlugin extends AbstractStatefulPlugin implements ChangeHandler {
    /**
     * Command used to apply a given style name to each of the text nodes from the current text selection.
     */
    private static final Command INLINE_STYLE_NAME = new Command("inlineStyleName");

    /**
     * Command used to apply a given style name to each of the block nodes touched by the current text selection.
     */
    private static final Command BLOCK_STYLE_NAME = new Command("blockStyleName");

    /**
     * The CSS class name used to mark a style name as selected (i.e. applied on the current text selection).
     */
    private static final String SELECTED = "selected";

    /**
     * The name of the boolean property that specifies if a style name is in-line or not.
     */
    private static final String INLINE = "inline";

    /**
     * The widget used to pick a style name.
     */
    private ListBox styleNamePicker;

    /**
     * The list of in-line style names.
     */
    private final List<OptionElement> inlineStyles = new ArrayList<OptionElement>();

    /**
     * The list of block style names.
     */
    private final List<OptionElement> blockStyles = new ArrayList<OptionElement>();

    /**
     * Stores the previous value for each of the style commands to speed up the style picker update.
     * 
     * @see #update(List, Command)
     */
    private final Map<Command, String> previousValue = new HashMap<Command, String>();

    /**
     * User interface extension for the editor tool bar.
     */
    private final FocusWidgetUIExtension toolBarExtension = new FocusWidgetUIExtension("toolbar");

    /**
     * {@inheritDoc}
     * 
     * @see AbstractStatefulPlugin#init(RichTextArea, Config)
     */
    public void init(RichTextArea textArea, Config config) {
        super.init(textArea, config);

        // Register custom executables.
        getTextArea().getCommandManager().registerCommand(INLINE_STYLE_NAME,
                new InlineStyleNameExecutable(textArea));
        getTextArea().getCommandManager().registerCommand(BLOCK_STYLE_NAME, new BlockStyleNameExecutable(textArea));

        if (getTextArea().getCommandManager().isSupported(INLINE_STYLE_NAME)) {
            initStyleNamePicker();

            registerTextAreaHandlers();
            getUIExtensionList().add(toolBarExtension);
        }
    }

    /**
     * Initialize the style name picker.
     */
    private void initStyleNamePicker() {
        styleNamePicker = new ListBox();
        styleNamePicker.setTitle(Strings.INSTANCE.stylePickerTitle());
        styleNamePicker.addStyleName("xStyleNamePicker");
        styleNamePicker.addItem(Strings.INSTANCE.stylePickerLabel(), "");
        saveRegistration(styleNamePicker.addChangeHandler(this));

        StyleDescriptorJSONParser parser = new StyleDescriptorJSONParser();
        for (StyleDescriptor descriptor : parser.parse(getConfig().getParameter("styleNames", "[]"))) {
            styleNamePicker.addItem(descriptor.getLabel(), descriptor.getName());
            NodeList<OptionElement> options = SelectElement.as(styleNamePicker.getElement()).getOptions();
            OptionElement option = options.getItem(options.getLength() - 1);
            option.setPropertyBoolean(INLINE, descriptor.isInline());
            (descriptor.isInline() ? inlineStyles : blockStyles).add(option);
        }

        if (blockStyles.size() > 0 && inlineStyles.size() > 0) {
            groupStyleNames(Strings.INSTANCE.styleBlockGroupLabel(), blockStyles);
            groupStyleNames(Strings.INSTANCE.styleInlineGroupLabel(), inlineStyles);
        }
        styleNamePicker.setSelectedIndex(0);

        toolBarExtension.addFeature("stylename", styleNamePicker);
    }

    /**
     * Group the given style names under the specified group.
     * 
     * @param groupLabel the group label
     * @param styleNames the style names to group
     */
    private void groupStyleNames(String groupLabel, List<OptionElement> styleNames) {
        OptGroupElement group = styleNamePicker.getElement().getOwnerDocument().createOptGroupElement();
        group.setLabel(groupLabel);
        styleNamePicker.getElement().appendChild(group);
        for (OptionElement styleName : styleNames) {
            group.appendChild(styleName);
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see AbstractStatefulPlugin#destroy()
     */
    public void destroy() {
        if (styleNamePicker != null) {
            styleNamePicker.removeFromParent();
            styleNamePicker = null;
        }

        blockStyles.clear();
        inlineStyles.clear();
        toolBarExtension.clearFeatures();

        super.destroy();
    }

    /**
     * {@inheritDoc}
     * 
     * @see ChangeHandler#onChange(ChangeEvent)
     */
    public void onChange(ChangeEvent event) {
        if (styleNamePicker == event.getSource() && styleNamePicker.isEnabled()) {
            int selectedIndex = styleNamePicker.getSelectedIndex();
            styleNamePicker.setSelectedIndex(0);
            // Ignore the first option which is used as the list box label.
            if (selectedIndex > 0) {
                // The method that returns the options from a select element is broken in WebKit browsers.
                // See http://code.google.com/p/google-web-toolkit/issues/detail?id=4916 .
                if (selectedIndex <= blockStyles.size()) {
                    toggleStyle(blockStyles.get(selectedIndex - 1));
                } else {
                    toggleStyle(inlineStyles.get(selectedIndex - blockStyles.size() - 1));
                }
            }
        }
    }

    /**
     * Toggles the style name corresponding to the given style option.
     * 
     * @param styleOption one of the style option from the style name list box
     */
    private void toggleStyle(OptionElement styleOption) {
        String styleName = styleOption.getValue();
        Command command = styleOption.getPropertyBoolean(INLINE) ? INLINE_STYLE_NAME : BLOCK_STYLE_NAME;
        getTextArea().setFocus(true);
        getTextArea().getCommandManager().execute(command, styleName);
    }

    /**
     * {@inheritDoc}
     * 
     * @see AbstractStatefulPlugin#update()
     */
    public void update() {
        if (styleNamePicker.isEnabled()) {
            update(blockStyles, BLOCK_STYLE_NAME);
            update(inlineStyles, INLINE_STYLE_NAME);
        }
    }

    /**
     * Updates the selected state of all the options in the specified list.
     * 
     * @param styleNameOptions the list of style name options to update
     * @param styleNameCommand the command used to retrieve the list of applied style names
     */
    private void update(List<OptionElement> styleNameOptions, Command styleNameCommand) {
        // Ignore if the list of options is empty.
        if (styleNameOptions.size() == 0) {
            return;
        }

        // Check if the style name picker needs to be updated.
        String appliedStyleNames = getTextArea().getCommandManager().getStringValue(styleNameCommand);
        String previouslyAppliedStyleNames = previousValue.get(styleNameCommand);
        if (appliedStyleNames.equals(previouslyAppliedStyleNames)) {
            return;
        }
        previousValue.put(styleNameCommand, appliedStyleNames);

        // Update the style name picker.
        Set<String> styleNames = new HashSet<String>(Arrays.asList(appliedStyleNames.split("\\s+")));
        for (OptionElement option : styleNameOptions) {
            if (styleNames.contains(option.getValue())) {
                option.addClassName(SELECTED);
            } else {
                option.removeClassName(SELECTED);
            }
        }

        // Some browsers (e.g. Internet Explorer) don't update the selected options if we don't redisplay the select.
        styleNamePicker.getElement().getStyle().setDisplay(Display.NONE);
        styleNamePicker.getElement().getStyle().clearDisplay();
    }
}