/* * 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; import org.xwiki.gwt.dom.client.Element; import org.xwiki.gwt.user.client.Config; import org.xwiki.gwt.user.client.StringUtils; import org.xwiki.gwt.wysiwyg.client.plugin.PluginFactoryManager; import org.xwiki.gwt.wysiwyg.client.syntax.SyntaxValidator; import org.xwiki.gwt.wysiwyg.client.syntax.SyntaxValidatorManager; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.event.dom.client.LoadEvent; import com.google.gwt.event.logical.shared.SelectionEvent; import com.google.gwt.event.logical.shared.SelectionHandler; import com.google.gwt.user.client.ui.TabPanel; import com.google.gwt.user.client.ui.Widget; /** * A what-you-see-is-what-you-get rich text editor. * * @version $Id: 9488480d9789a8a13c24196c6fa6f1b3ccf5d7df $ */ public class WysiwygEditor extends RichTextEditorController { /** * The interface of the WYSIWYG editor. It can be either a {@link RichTextEditor} or a {@link TabPanel} containing * the {@link RichTextEditor} and the {@link PlainTextEditor}. */ private final Widget ui; /** * The configuration object. */ private final WysiwygEditorConfig config; /** * The object that will handle the switch between the source editor and the rich text editor. */ private final WysiwygEditorTabSwitchHandler switcher; /** * Flag indicating if the rich text area was loaded. */ private boolean richTextAreaLoaded; /** * Flag indicating if the rich text editor was initialized. The rich text editor must be initialized after the rich * text area is loaded (after it is attached to the document). */ private boolean richTextEditorInitialized; /** * Creates a new WYSIWYG editor. * * @param config the configuration source * @param svm the syntax validation manager used for enabling or disabling plugin features * @param pfm the plugin factory manager used to instantiate plugins */ public WysiwygEditor(Config config, SyntaxValidatorManager svm, PluginFactoryManager pfm) { this(new WysiwygEditorConfig(config), svm, pfm); } /** * Creates a new WYSIWYG editor. * * @param config the configuration object * @param svm the syntax validation manager used for enabling or disabling plugin features * @param pfm the plugin factory manager used to instantiate plugins */ public WysiwygEditor(WysiwygEditorConfig config, SyntaxValidatorManager svm, PluginFactoryManager pfm) { super(new RichTextEditor(), config.getConfigurationSource(), pfm, getSyntaxValidator(svm, config)); this.config = config; // Initialize the WYSIWYG/Source tab switcher. Even if the editor tabs are disabled, this object is still useful // to convert the source text to HTML (when initializing the rich text area). switcher = new WysiwygEditorTabSwitchHandler(this); // Initialize the user interface. if (config.isTabbed()) { ui = createTabPanel(); initializePlainTextArea(); } else { ui = getRichTextEditor(); // Disable the rich text area if the wrapped plain text area is disabled or read-only. getRichTextEditor().getTextArea().setEnabled(this.config.isEnabled()); } // Resize the rich text area and hide the hook. Element hook = config.getHook(); getRichTextEditor().getTextArea().setHeight(Math.max(hook.getOffsetHeight(), 100) + "px"); hook.getStyle().setDisplay(Display.NONE); } /** * @param svm the object used to retrieve the syntax validator * @param config the configuration object * @return a validator for the configured syntax, if it exists, otherwise a validator for the default syntax */ private static SyntaxValidator getSyntaxValidator(SyntaxValidatorManager svm, WysiwygEditorConfig config) { SyntaxValidator sv = svm.getSyntaxValidator(config.getSyntax()); if (sv == null) { sv = svm.getSyntaxValidator(WysiwygEditorConfig.DEFAULT_SYNTAX); } return sv; } @Override public void onLoad(LoadEvent event) { if (event.getSource() == getRichTextEditor().getTextArea() && !richTextAreaLoaded) { richTextAreaLoaded = true; boolean inputConverted = config.isInputConverted(); if (inputConverted && StringUtils.isEmpty(config.getTemplateURL())) { // We don't have to convert the input value nor to load a rich text area template. getRichTextEditor().getTextArea().setHTML(config.getInputValue()); super.onLoad(event); } else { // If input value is already converted then we just load the rich text area template. switcher.convertToHTML(inputConverted ? "" : config.getInputValue()); } } } /** * Initializes the rich text editor if it wasn't already initialized. */ protected void maybeInitializeRichTextEditor() { if (!richTextEditorInitialized) { richTextEditorInitialized = true; if (config.isInputConverted()) { // The rich text area template was loaded. We can now set the inner HTML. getRichTextEditor().getTextArea().setHTML(config.getInputValue()); } maybeInitialize(); } } /** * Initializes the plain text area. */ private void initializePlainTextArea() { if (config.isInputConverted()) { switcher.convertFromHTML(config.getInputValue()); } else { getPlainTextEditor().getTextArea().setText(config.getInputValue()); } } /** * Build the editor tab panel. This panel contains two tabs, one for the WYSIWYG editor and one for the source * editor. * * @return the newly created tab panel */ private TabPanel createTabPanel() { PlainTextEditor plainTextEditor = new PlainTextEditor(config.getHook()); TabPanel tabs = new TabPanel(); tabs.add(getRichTextEditor(), Strings.INSTANCE.wysiwyg()); tabs.add(plainTextEditor, Strings.INSTANCE.source()); tabs.setStyleName("xRichTextEditorTabPanel"); tabs.setAnimationEnabled(false); tabs.selectTab(config.getSelectedTabIndex()); // Enable the appropriate text area based on the type of input (source or HTML). boolean inputConverted = config.isInputConverted(); boolean editorEnabled = this.config.isEnabled(); plainTextEditor.getTextArea().setEnabled(editorEnabled && !inputConverted); getRichTextEditor().getTextArea().setEnabled(editorEnabled && inputConverted); saveRegistration(tabs.addBeforeSelectionHandler(switcher)); saveRegistration(tabs.addSelectionHandler(new SelectionHandler<Integer>() { public void onSelection(SelectionEvent<Integer> event) { // Cache the active text area. config.setSelectedTabIndex(event.getSelectedItem()); } })); saveRegistration(tabs.addSelectionHandler(switcher)); return tabs; } /** * In a Model-View-Controller architecture the UI represents the View component, while this class represents the * Controller. The model could be considered the DOM document edited. * * @return the user interface of the editor, i.e. the rich text editor if tabs are disabled, the tab panel otherwise */ public Widget getUI() { return ui; } /** * Get the plain text editor. * * @return the plain text editor, {@code null} if it does not exist */ public PlainTextEditor getPlainTextEditor() { return config.isTabbed() ? (PlainTextEditor) ((TabPanel) ui).getWidget(WysiwygEditorConfig.SOURCE_TAB_INDEX) : null; } /** * @return the index of the currently selected tab */ public int getSelectedTab() { return ui instanceof TabPanel ? ((TabPanel) ui).getTabBar().getSelectedTab() : WysiwygEditorConfig.WYSIWYG_TAB_INDEX; } /** * Sets the selected tab. * * @param index the tab index */ public void setSelectedTab(int index) { if (ui instanceof TabPanel) { ((TabPanel) ui).selectTab(index); } } /** * @return the configuration object */ public WysiwygEditorConfig getConfig() { return config; } @Override public void destroy() { super.destroy(); // Detach the user interface. ui.removeFromParent(); } }