/*
* 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.link;
import java.util.HashMap;
import java.util.Map;
import org.xwiki.gwt.dom.client.Element;
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.user.client.ui.rta.cmd.Executable;
import org.xwiki.gwt.user.client.ui.wizard.Wizard;
import org.xwiki.gwt.user.client.ui.wizard.WizardListener;
import org.xwiki.gwt.wysiwyg.client.plugin.internal.AbstractPlugin;
import org.xwiki.gwt.wysiwyg.client.plugin.link.exec.CreateLinkExecutable;
import org.xwiki.gwt.wysiwyg.client.plugin.link.exec.UnlinkExecutable;
import org.xwiki.gwt.wysiwyg.client.plugin.link.ui.LinkWizard;
import org.xwiki.gwt.wysiwyg.client.plugin.link.ui.LinkWizard.LinkWizardStep;
import org.xwiki.gwt.wysiwyg.client.wiki.WikiServiceAsync;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
/**
* Rich text editor plug-in for inserting links, using a dialog to get link settings from the user. It installs a menu
* bar extension, with entries for all its actions.
*
* @version $Id: 124fd71a1391c03882c86cf093744a0f9a405891 $
*/
public class LinkPlugin extends AbstractPlugin implements WizardListener
{
/**
* The wizard to create a link.
*/
private Wizard linkWizard;
/**
* The menu extension of this plugin.
*/
private LinkMenuExtension menuExtension;
/**
* The link metadata extractor, to handle the link metadata.
*/
private LinkMetaDataExtractor metaDataExtractor;
/**
* The empty link filter, to prevent the submission of empty links.
*/
private EmptyLinkFilter linkFilter;
/**
* Map of the original link related executables, which will be replaced with custom executables by this plugin.
*/
private Map<Command, Executable> originalExecutables;
/**
* The object used to create link configuration objects.
*/
private LinkConfigFactory linkConfigFactory;
/**
* The service used to access the wiki.
*/
private final WikiServiceAsync wikiService;
/**
* The object used to serialize a {@link LinkConfig} instance to JSON.
*/
private final LinkConfigJSONSerializer linkConfigJSONSerializer = new LinkConfigJSONSerializer();
/**
* Creates a new link plugin that will use the specified wiki service.
*
* @param wikiService the service used to access the wiki
*/
public LinkPlugin(WikiServiceAsync wikiService)
{
this.wikiService = wikiService;
}
@Override
public void init(RichTextArea textArea, Config config)
{
super.init(textArea, config);
// Register custom executables.
Executable createLinkExec =
getTextArea().getCommandManager().registerCommand(Command.CREATE_LINK, new CreateLinkExecutable(textArea));
Executable unlinkExec =
getTextArea().getCommandManager().registerCommand(Command.UNLINK, new UnlinkExecutable(textArea));
if (createLinkExec != null || unlinkExec != null) {
originalExecutables = new HashMap<Command, Executable>();
}
if (createLinkExec != null) {
originalExecutables.put(Command.CREATE_LINK, createLinkExec);
}
if (unlinkExec != null) {
originalExecutables.put(Command.UNLINK, unlinkExec);
}
menuExtension = new LinkMenuExtension(this);
getUIExtensionList().add(menuExtension);
// Hack: We can access the menus where each menu item was placed only after the main menu bar is initialized,
// which happens after all the plugins are loaded.
Scheduler.get().scheduleDeferred(new ScheduledCommand()
{
public void execute()
{
menuExtension.registerAttachHandlers();
}
});
// Initialize the meta data extractor to handle link meta data.
metaDataExtractor = new LinkMetaDataExtractor();
// Do the initial extracting on the loaded document.
metaDataExtractor.onInnerHTMLChange((Element) getTextArea().getDocument().getDocumentElement());
getTextArea().getDocument().addInnerHTMLListener(metaDataExtractor);
// Create an empty link handler and add it to the command manager
linkFilter = new EmptyLinkFilter(getTextArea());
getTextArea().getCommandManager().addCommandListener(linkFilter);
// Initialize the link configuration factory.
linkConfigFactory = new LinkConfigFactory(textArea);
}
@Override
public void destroy()
{
// Restore previous executables.
if (originalExecutables != null) {
for (Map.Entry<Command, Executable> entry : originalExecutables.entrySet()) {
getTextArea().getCommandManager().registerCommand(entry.getKey(), entry.getValue());
}
}
if (metaDataExtractor != null) {
getTextArea().getDocument().removeInnerHTMLListener(metaDataExtractor);
metaDataExtractor = null;
}
// Remove the empty link filter from the text area.
getTextArea().getCommandManager().removeCommandListener(linkFilter);
// Destroy menu extension.
menuExtension.clearFeatures();
super.destroy();
}
/**
* Handles the creation of a link of the specified type: prepares and shows the link wizard.
*
* @param linkType the type of link to insert
*/
public void onLinkInsert(LinkConfig.LinkType linkType)
{
LinkConfig linkConfig = linkConfigFactory.createLinkConfig();
linkConfig.setType(linkType);
getLinkWizard().start(LinkWizardStep.LINK_REFERENCE_PARSER.toString(), linkConfig);
}
/**
* Handles the edit of a link: prepares and shows the link wizard.
*/
public void onLinkEdit()
{
getLinkWizard().start(LinkWizardStep.LINK_REFERENCE_PARSER.toString(), linkConfigFactory.createLinkConfig());
}
/**
* @return the link wizard
*/
private Wizard getLinkWizard()
{
if (linkWizard == null) {
linkWizard = new LinkWizard(this.getConfig(), wikiService);
linkWizard.addWizardListener(this);
}
return linkWizard;
}
/**
* Executed when the unlink button is clicked.
*/
public void onUnlink()
{
getTextArea().setFocus(true);
getTextArea().getCommandManager().execute(Command.UNLINK);
}
/**
* {@inheritDoc}
* <p>
* Handles wizard finish by creating the link HTML block from the {@link LinkConfig} setup through the wizard and
* executing the {@link Command#CREATE_LINK} with it.
* </p>
*
* @see WizardListener#onFinish(Wizard, Object)
*/
@Override
public void onFinish(Wizard sender, Object result)
{
// Return the focus to the rich text area.
getTextArea().setFocus(true);
// Insert or update the link.
String linkJSON = linkConfigJSONSerializer.serialize((LinkConfig) result);
getTextArea().getCommandManager().execute(Command.CREATE_LINK, linkJSON);
}
@Override
public void onCancel(Wizard sender)
{
// Return the focus to the text area.
getTextArea().setFocus(true);
}
}