/*
* 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.image.exec;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.xwiki.gwt.dom.client.Range;
import org.xwiki.gwt.dom.client.Style;
import org.xwiki.gwt.user.client.StringUtils;
import org.xwiki.gwt.user.client.URLUtils;
import org.xwiki.gwt.user.client.ui.rta.RichTextArea;
import org.xwiki.gwt.user.client.ui.rta.cmd.internal.AbstractInsertElementExecutable;
import org.xwiki.gwt.wysiwyg.client.plugin.image.ImageConfig;
import org.xwiki.gwt.wysiwyg.client.plugin.image.ImageConfigDOMReader;
import org.xwiki.gwt.wysiwyg.client.plugin.image.ImageConfigDOMWriter;
import org.xwiki.gwt.wysiwyg.client.plugin.image.ImageConfigJSONParser;
import org.xwiki.gwt.wysiwyg.client.plugin.image.ImageConfigJSONSerializer;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.dom.client.Node;
/**
* Handles the insertion of an image, passed through its corresponding HTML block.
*
* @version $Id: 0e54f6bf8d5deccab0bbe950d17b29410395421c $
*/
public class InsertImageExecutable extends AbstractInsertElementExecutable<ImageConfig, ImageElement>
{
/**
* Creates a new executable that can be used to insert images in the specified rich text area.
*
* @param rta the execution target
*/
public InsertImageExecutable(RichTextArea rta)
{
super(rta);
configDOMReader = new ImageConfigDOMReader();
configDOMWriter = GWT.create(ImageConfigDOMWriter.class);
configJSONParser = new ImageConfigJSONParser();
configJSONSerializer = new ImageConfigJSONSerializer();
}
@Override
protected void write(ImageConfig config, ImageElement image)
{
// Backup the new image URL.
String url = config.getUrl();
boolean internal = !StringUtils.areEqual(config.getReference(), url);
if (internal) {
// We keep the current image URL because we want to resize the current image before requesting the new
// image. This way we can use the computed width/height to adjust the URL of the new image so that it is
// resized on the server.
config.setUrl(image.getSrc());
}
super.write(config, image);
if (internal) {
// Adjust the new image URL so that the image is resized on the server.
updateURL(image, url);
}
}
@Override
protected String getCacheKeyPrefix()
{
return InsertImageExecutable.class.getName();
}
/**
* {@inheritDoc}
* <p>
* Gets the image element in the current selection.
* </p>
*
* @see AbstractInsertElementExecutable#getSelectedElement()
*/
protected ImageElement getSelectedElement()
{
// Check if the current selection perfectly wraps an image.
// We expect the selection to have at least one range, otherwise this executable wouldn't be enabled.
Range currentRange = rta.getDocument().getSelection().getRangeAt(0);
Node startContainer = currentRange.getStartContainer();
Node endContainer = currentRange.getEndContainer();
if (startContainer == endContainer && startContainer.getNodeType() == Node.ELEMENT_NODE
&& (currentRange.getEndOffset() - currentRange.getStartOffset() == 1)) {
// Check that the node inside is an image.
Node nodeInside = startContainer.getChildNodes().getItem(currentRange.getStartOffset());
if (nodeInside.getNodeType() == Node.ELEMENT_NODE && nodeInside.getNodeName().equalsIgnoreCase("img")) {
return (ImageElement) nodeInside;
}
}
return null;
}
@Override
protected ImageElement newElement()
{
ImageElement image = rta.getDocument().createImageElement();
// Ensure the image URL is initially set. Use a blank image just to be able to determine the width and height of
// the image element in pixels (matching the width/height attributes or the style attribute) before requesting
// the real image. The computed width and height are used to adjust the real image URL so that the image is
// resized on the server.
image.setSrc(GWT.getModuleBaseURL() + "clear.cache.gif");
return image;
}
/**
* Updates the URL of the given image. If image width and height are specified they are added to the query string in
* order to resize the image on the server side.
*
* @param image the image whose URL is updated
* @param url the new image URL
*/
private void updateURL(ImageElement image, String url)
{
int width = image.getWidth();
int height = image.getHeight();
// Put width and height information in the query string in order to resize images on the server side.
Map<String, List<String>> parameters = URLUtils.parseQueryString(URLUtils.getQueryString(url));
parameters.remove(Style.WIDTH);
parameters.remove(Style.HEIGHT);
parameters.remove("keepAspectRatio");
if (isDimensionSpecified(image, Style.WIDTH)) {
parameters.put(Style.WIDTH, Arrays.asList(new String[] {String.valueOf(width)}));
if (isDimensionSpecified(image, Style.HEIGHT)) {
parameters.put(Style.HEIGHT, Arrays.asList(new String[] {String.valueOf(height)}));
}
} else {
// Width is unspecified.
if (isDimensionSpecified(image, Style.HEIGHT)) {
parameters.put(Style.HEIGHT, Arrays.asList(new String[] {String.valueOf(height)}));
} else {
// If image width and height are unspecified limit the image width to fit the rich text area (leaving
// space for the vertical scroll bar).
int widthLimit = image.getOwnerDocument().getClientWidth() - 22;
if (widthLimit > 0) {
parameters.put(Style.WIDTH, Arrays.asList(new String[] {String.valueOf(widthLimit)}));
}
}
}
// Update the image URL.
image.setSrc(URLUtils.setQueryString(url, parameters));
}
/**
* @param image an image element
* @param dimension either {@code width} or {@code height}
* @return {@code true} if the specified dimension is explicitly set on the given image element
*/
private boolean isDimensionSpecified(ImageElement image, String dimension)
{
return image.getPropertyInt(dimension) > 0
&& (!StringUtils.isEmpty(image.getStyle().getProperty(dimension)) || image.hasAttribute(dimension));
}
}