/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.server.headlessclient;
import java.awt.Insets;
import java.awt.Point;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.wicket.Component;
import org.apache.wicket.Component.IVisitor;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.Request;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.Session;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.mozilla.javascript.ScriptRuntime;
import com.servoy.base.scripting.api.IJSEvent.EventType;
import com.servoy.j2db.DesignModeCallbacks;
import com.servoy.j2db.FormController;
import com.servoy.j2db.dnd.DRAGNDROP;
import com.servoy.j2db.scripting.IScriptableProvider;
import com.servoy.j2db.scripting.JSEvent;
import com.servoy.j2db.scripting.info.CLIENTDESIGN;
import com.servoy.j2db.server.headlessclient.dataui.AbstractServoyDefaultAjaxBehavior;
import com.servoy.j2db.server.headlessclient.dataui.TemplateGenerator;
import com.servoy.j2db.server.headlessclient.dataui.WebDataCalendar;
import com.servoy.j2db.server.headlessclient.dataui.WebDataRenderer;
import com.servoy.j2db.server.headlessclient.dataui.WebEventExecutor;
import com.servoy.j2db.server.headlessclient.dnd.DraggableBehavior;
import com.servoy.j2db.server.headlessclient.yui.YUILoader;
import com.servoy.j2db.ui.IComponent;
import com.servoy.j2db.ui.IProviderStylePropertyChanges;
import com.servoy.j2db.ui.ISupportWebBounds;
import com.servoy.j2db.ui.ITabPanel;
import com.servoy.j2db.ui.runtime.IRuntimeComponent;
import com.servoy.j2db.ui.runtime.IRuntimeInputComponent;
import com.servoy.j2db.util.Utils;
/**
* @author jcompagner
* @author jblok
*/
@SuppressWarnings("nls")
public class DesignModeBehavior extends AbstractServoyDefaultAjaxBehavior
{
public static final String ACTION_RESIZE = "aResize";
public static final String ACTION_SELECT = "aSelect";
public static final String PARAM_RESIZE_HEIGHT = "resizeHeight";
public static final String PARAM_RESIZE_WIDTH = "resizeWidth";
public static final String PARAM_IS_DBLCLICK = "isDblClick";
public static final String PARAM_IS_RIGHTCLICK = "isRightClick";
public static final String PARAM_IS_CTRL_KEY = "isCtrlKey";
private DesignModeCallbacks callback;
private FormController controller;
private IComponent onDragComponent;
private final HashMap<IComponent, String> onSelectComponents = new HashMap<IComponent, String>();
/**
* @see org.apache.wicket.ajax.AbstractDefaultAjaxBehavior#renderHead(org.apache.wicket.markup.html.IHeaderResponse)
*/
@Override
public void renderHead(IHeaderResponse response)
{
super.renderHead(response);
YUILoader.renderResize(response);
final ArrayList<Component> markupIds = new ArrayList<Component>();
final ArrayList<Component> dropMarkupIds = new ArrayList<Component>();
((MarkupContainer)getComponent()).visitChildren(IComponent.class, new IVisitor<Component>()
{
public Object component(Component component)
{
if (!(component instanceof WebDataRenderer))
{
markupIds.add(component);
if (component instanceof ITabPanel || component instanceof WebDataCalendar)
{
return IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
}
}
else if (component instanceof WebDataRenderer)
{
dropMarkupIds.add(component);
}
return IVisitor.CONTINUE_TRAVERSAL;
}
});
if (markupIds.size() > 0)
{
boolean webAnchorsEnabled = Utils.getAsBoolean(((WebClientSession)Session.get()).getWebClient().getRuntimeProperties().get("enableAnchors"));
//WebClientSession webClientSession = (WebClientSession)getSession();
//WebClient webClient = webClientSession.getWebClient();
ArrayList<String> selectedComponentsId = new ArrayList<String>();
StringBuilder sb = new StringBuilder(markupIds.size() * 10);
sb.append("Servoy.ClientDesign.attach({");
for (int i = 0; i < markupIds.size(); i++)
{
Component component = markupIds.get(i);
Object clientdesign_handles = null;
if (component instanceof IScriptableProvider && ((IScriptableProvider)component).getScriptObject() instanceof IRuntimeComponent)
{
IRuntimeComponent sbmc = (IRuntimeComponent)((IScriptableProvider)component).getScriptObject();
if (sbmc.getName() == null) continue; //skip, elements with no name are not usable in CD
clientdesign_handles = sbmc.getClientProperty(CLIENTDESIGN.HANDLES);
Object clientdesign_selectable = sbmc.getClientProperty(CLIENTDESIGN.SELECTABLE);
if (clientdesign_selectable != null && !Utils.getAsBoolean(clientdesign_selectable)) continue; //skip
}
String padding = "0px 0px 0px 0px"; //$NON-NLS-1$
if (component instanceof ISupportWebBounds)
{
Insets p = ((ISupportWebBounds)component).getPaddingAndBorder();
if (p != null) padding = "0px " + (p.left + p.right) + "px " + (p.bottom + p.top) + "px 0px";
}
boolean editable = false;
if (component instanceof IScriptableProvider && ((IScriptableProvider)component).getScriptObject() instanceof IRuntimeInputComponent)
{
editable = ((IRuntimeInputComponent)((IScriptableProvider)component).getScriptObject()).isEditable();
}
String compId;
if (webAnchorsEnabled && component instanceof IScriptableProvider &&
((IScriptableProvider)component).getScriptObject() instanceof IRuntimeComponent &&
needsWrapperDivForAnchoring(((IRuntimeComponent)((IScriptableProvider)component).getScriptObject()).getElementType(), editable))
{
compId = component.getMarkupId() + TemplateGenerator.WRAPPER_SUFFIX;
}
else
{
compId = component.getMarkupId();
}
sb.append(compId);
if (component instanceof IComponent)
{
Iterator<IComponent> selectedComponentsIte = onSelectComponents.keySet().iterator();
IComponent c;
while (selectedComponentsIte.hasNext())
{
c = selectedComponentsIte.next();
if (c.getName().equals(((IComponent)component).getName()))
{
onSelectComponents.put(c, compId);
selectedComponentsId.add(compId);
break;
}
}
}
sb.append(":['");
sb.append(padding);
sb.append("'");
if (clientdesign_handles instanceof Object[])
{
sb.append(",[");
Object[] array = (Object[])clientdesign_handles;
for (Object element : array)
{
sb.append('\'');
sb.append(ScriptRuntime.escapeString(element.toString()));
sb.append("',");
}
sb.setLength(sb.length() - 1); //rollback last comma
sb.append("]");
}
sb.append("],");
}
sb.setLength(sb.length() - 1); //rollback last comma
sb.append("},'" + getCallbackUrl() + "')");
if (selectedComponentsId.size() > 0)
{
for (int i = 0; i < selectedComponentsId.size(); i++)
{
sb.append(";Servoy.ClientDesign.selectedElementId[").append(i).append("]='").append(selectedComponentsId.get(i)).append("';");
}
sb.append("Servoy.ClientDesign.reattach();");
}
response.renderOnDomReadyJavascript(sb.toString());
// if (dropMarkupIds.size() > 0)
// {
// StringBuilder attachDrop = new StringBuilder();
// attachDrop.append("attachDrop([");
// for (int i = 0; i < dropMarkupIds.size(); i++)
// {
// Component component = dropMarkupIds.get(i);
// attachDrop.append("'");
// attachDrop.append(component.getMarkupId());
// attachDrop.append("',");
// }
// attachDrop.setLength(attachDrop.length() - 1);
// attachDrop.append("])");
// response.renderOnDomReadyJavascript(attachDrop.toString());
// }
}
}
private boolean needsWrapperDivForAnchoring(String type, boolean editable)
{
// this needs to be in sync with WebAnchoringHelper.needsWrapperDivForAnchoring(Field field)
// and TemplateGenerator.isButton(GraphicalComponent label)
return IRuntimeComponent.PASSWORD.equals(type) || IRuntimeComponent.TEXT_AREA.equals(type) || IRuntimeComponent.COMBOBOX.equals(type) ||
IRuntimeComponent.TYPE_AHEAD.equals(type) || IRuntimeComponent.TEXT_FIELD.equals(type) || (IRuntimeComponent.HTML_AREA.equals(type) && editable) ||
(IRuntimeComponent.LISTBOX.equals(type)) || (IRuntimeComponent.MULTISELECT_LISTBOX.equals(type)) || IRuntimeComponent.BUTTON.equals(type);
}
/**
* @see org.apache.wicket.ajax.AbstractDefaultAjaxBehavior#respond(org.apache.wicket.ajax.AjaxRequestTarget)
*/
@Override
protected void respond(AjaxRequestTarget target)
{
Request request = RequestCycle.get().getRequest();
String action = request.getParameter(DraggableBehavior.PARAM_ACTION);
String id = extractId(request.getParameter(DraggableBehavior.PARAM_DRAGGABLE_ID));
if (id != null)
{
final String finalId = id.endsWith(TemplateGenerator.WRAPPER_SUFFIX) ? id.substring(0, id.length() - 8) : id;
MarkupContainer comp = (MarkupContainer)getComponent();
Component child = (Component)comp.visitChildren(Component.class, new IVisitor<Component>()
{
public Object component(Component component)
{
String markupId = component.getMarkupId();
if (finalId.equals(markupId)) return component;
return IVisitor.CONTINUE_TRAVERSAL;
}
});
if (action != null)
{
int height = stripUnitPart(request.getParameter(PARAM_RESIZE_HEIGHT));
int width = stripUnitPart(request.getParameter(PARAM_RESIZE_WIDTH));
int x = stripUnitPart(request.getParameter(DraggableBehavior.PARAM_X));
int y = stripUnitPart(request.getParameter(DraggableBehavior.PARAM_Y));
if (action.equals(ACTION_SELECT))
{
if (!(child instanceof IComponent)) onSelectComponents.clear();
else
{
boolean isSelectionRemove = false;
if (!Boolean.parseBoolean(request.getParameter(PARAM_IS_CTRL_KEY))) onSelectComponents.clear();
else
{
isSelectionRemove = onSelectComponents.remove(child) != null;
}
IComponent[] param = onSelectComponents.keySet().toArray(
new IComponent[isSelectionRemove ? onSelectComponents.size() : onSelectComponents.size() + 1]);
if (!isSelectionRemove) param[onSelectComponents.size()] = (IComponent)child;
Object ret = callback.executeOnSelect(getJSEvent(EventType.action, 0, new Point(x, y), param));
if (ret instanceof Boolean && !((Boolean)ret).booleanValue())
{
onSelectComponents.clear();
}
else
{
if (!isSelectionRemove) onSelectComponents.put((IComponent)child, id);
StringBuilder idsArray = new StringBuilder("new Array(");
Iterator<String> idsIte = onSelectComponents.values().iterator();
while (idsIte.hasNext())
{
idsArray.append('\'').append(idsIte.next()).append('\'');
if (idsIte.hasNext()) idsArray.append(',');
}
idsArray.append(')');
target.appendJavascript("Servoy.ClientDesign.attachElements(" + idsArray.toString() + ");");
}
if (Boolean.parseBoolean(request.getParameter(PARAM_IS_RIGHTCLICK)))
{
callback.executeOnRightClick(getJSEvent(EventType.rightClick, 0, new Point(x, y), param));
}
else if (Boolean.parseBoolean(request.getParameter(PARAM_IS_DBLCLICK)))
{
callback.executeOnDblClick(getJSEvent(EventType.doubleClick, 0, new Point(x, y), param));
}
}
WebEventExecutor.generateResponse(target, getComponent().getPage());
target.appendJavascript("Servoy.ClientDesign.clearClickTimer();");
return;
}
if (child instanceof IComponent)
{
if (!onSelectComponents.containsKey(child))
{
onSelectComponents.put((IComponent)child, id);
}
if (action.equals(ACTION_RESIZE))
{
if (width != -1 && height != -1)
{
if (child instanceof ISupportWebBounds)
{
Insets paddingAndBorder = ((ISupportWebBounds)child).getPaddingAndBorder();
if (paddingAndBorder != null)
{
height += paddingAndBorder.bottom + paddingAndBorder.top;
width += paddingAndBorder.left + paddingAndBorder.right;
}
}
if (child instanceof IScriptableProvider)
{
((IRuntimeComponent)((IScriptableProvider)child).getScriptObject()).setSize(width, height);
((IRuntimeComponent)((IScriptableProvider)child).getScriptObject()).setLocation(x, y);
}
if (child instanceof IProviderStylePropertyChanges) ((IProviderStylePropertyChanges)child).getStylePropertyChanges().setRendered();
}
callback.executeOnResize(getJSEvent(EventType.onDrop, 0, new Point(x, y), new IComponent[] { (IComponent)child }));
}
else if (action.equals(DraggableBehavior.ACTION_DRAG_START))
{
Object onDragAllowed = callback.executeOnDrag(getJSEvent(EventType.onDrag, 0, new Point(x, y),
onSelectComponents.keySet().toArray(new IComponent[onSelectComponents.size()])));
if ((onDragAllowed instanceof Boolean && !((Boolean)onDragAllowed).booleanValue()) ||
(onDragAllowed instanceof Number && ((Number)onDragAllowed).intValue() == DRAGNDROP.NONE))
{
onDragComponent = null;
}
else
{
onDragComponent = (IComponent)child;
}
WebEventExecutor.generateResponse(target, getComponent().getPage());
return;
}
else
{
if (child == onDragComponent)
{
if (x != -1 && y != -1)
{
((IRuntimeComponent)((IScriptableProvider)child).getScriptObject()).setLocation(x, y);
if (child instanceof IProviderStylePropertyChanges)
{
// test if it is wrapped
if ((child).getParent() instanceof WrapperContainer)
{
// call for the changes on the wrapper container so that it will copy the right values over
WrapperContainer wrapper = (WrapperContainer)(child).getParent();
wrapper.getStylePropertyChanges().getChanges();
wrapper.getStylePropertyChanges().setRendered();
}
((IProviderStylePropertyChanges)child).getStylePropertyChanges().setRendered();
}
}
callback.executeOnDrop(getJSEvent(EventType.onDrop, 0, new Point(x, y),
onSelectComponents.keySet().toArray(new IComponent[onSelectComponents.size()])));
}
if (Boolean.parseBoolean(request.getParameter(PARAM_IS_DBLCLICK)))
{
callback.executeOnDblClick(getJSEvent(EventType.doubleClick, 0, new Point(x, y), new IComponent[] { (IComponent)child }));
}
}
}
}
}
WebEventExecutor.generateResponse(target, getComponent().getPage());
target.appendJavascript("Servoy.ClientDesign.reattach();");
}
/**
* @param parameter
* @return
*/
private String extractId(String id)
{
if (id != null && id.endsWith("_wrap"))
{
return id.substring(0, id.length() - 5);
}
return id;
}
private int stripUnitPart(String str)
{
if (str == null || str.trim().equals("") || "auto".equals(str)) return -1;
if (str.endsWith("px") || str.endsWith("pt"))
{
return Integer.parseInt(str.substring(0, str.length() - 2));
}
return Integer.parseInt(str);
}
/**
* @see org.apache.wicket.behavior.AbstractBehavior#isEnabled(org.apache.wicket.Component)
*/
@Override
public boolean isEnabled(Component component)
{
return super.isEnabled(component) && callback != null;
}
public void setDesignModeCallback(DesignModeCallbacks callback, FormController controller)
{
this.callback = callback;
this.controller = controller;
}
public DesignModeCallbacks getDesignModeCallback()
{
return callback;
}
private JSEvent getJSEvent(EventType type, int modifiers, Point point, IComponent[] selected)
{
JSEvent event = new JSEvent();
event.setFormName(controller.getName());
event.setType(type);
event.setModifiers(modifiers);
event.setLocation(point);
List<Object> selection = new ArrayList<Object>();
if (selected != null)
{
for (IComponent component : selected)
{
if (component instanceof IScriptableProvider)
{
selection.add(0, ((IScriptableProvider)component).getScriptObject());
}
else
{
selection.add(0, component);
}
}
}
event.setData(selection.toArray());
//event.setSource(e)
return event;
}
public String[] getSelectedComponentsNames()
{
Set<IComponent> selectedComponents = onSelectComponents.keySet();
if (selectedComponents.size() > 0)
{
ArrayList<String> selectedComponentsNames = new ArrayList<String>();
Iterator<IComponent> selectedComponentsIte = selectedComponents.iterator();
while (selectedComponentsIte.hasNext())
selectedComponentsNames.add(selectedComponentsIte.next().getName());
return selectedComponentsNames.toArray(new String[selectedComponentsNames.size()]);
}
return null;
}
public void setSelectedComponents(String[] selectedComponentsNames)
{
onSelectComponents.clear();
if (selectedComponentsNames != null && selectedComponentsNames.length > 0)
{
IComponent c;
String compId;
boolean webAnchorsEnabled = Utils.getAsBoolean(((WebClientSession)Session.get()).getWebClient().getRuntimeProperties().get("enableAnchors"));
boolean editable;
for (String selectedComponentName : selectedComponentsNames)
{
c = getWicketComponentForName(selectedComponentName);
editable = false;
if (c instanceof IScriptableProvider && ((IScriptableProvider)c).getScriptObject() instanceof IRuntimeInputComponent)
{
editable = ((IRuntimeInputComponent)((IScriptableProvider)c).getScriptObject()).isEditable();
}
if (webAnchorsEnabled && c instanceof IScriptableProvider && ((IScriptableProvider)c).getScriptObject() instanceof IRuntimeComponent &&
needsWrapperDivForAnchoring(((IRuntimeComponent)((IScriptableProvider)c).getScriptObject()).getElementType(), editable))
{
compId = ((Component)c).getMarkupId() + TemplateGenerator.WRAPPER_SUFFIX;
}
else
{
compId = ((Component)c).getMarkupId();
}
onSelectComponents.put(c, compId);
}
}
}
private IComponent getWicketComponentForName(final String componentName)
{
if (componentName != null)
{
Component bindedComponent = getComponent();
if (bindedComponent != null)
{
WebForm parentWebForm = bindedComponent.findParent(WebForm.class);
if (parentWebForm != null)
{
return (IComponent)parentWebForm.visitChildren(IComponent.class, new IVisitor<Component>()
{
public Object component(Component component)
{
if (componentName.equals(((IComponent)component).getName()))
{
return component;
}
return IVisitor.CONTINUE_TRAVERSAL;
}
});
}
}
}
return null;
}
}