/*
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.smart.dataui;
import java.applet.Applet;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.plaf.ColorUIResource;
import org.mozilla.javascript.Undefined;
import com.servoy.j2db.ControllerUndoManager;
import com.servoy.j2db.FormController;
import com.servoy.j2db.IApplication;
import com.servoy.j2db.IScriptExecuter;
import com.servoy.j2db.dataprocessing.DataAdapterList;
import com.servoy.j2db.dataprocessing.FindState;
import com.servoy.j2db.dataprocessing.IDisplay;
import com.servoy.j2db.dataprocessing.IDisplayData;
import com.servoy.j2db.dataprocessing.IDisplayRelatedData;
import com.servoy.j2db.dataprocessing.IFoundSetInternal;
import com.servoy.j2db.dataprocessing.IRecordInternal;
import com.servoy.j2db.dataprocessing.PrototypeState;
import com.servoy.j2db.dnd.DRAGNDROP;
import com.servoy.j2db.dnd.FormDataTransferHandler;
import com.servoy.j2db.dnd.ISupportDragNDropTextTransfer;
import com.servoy.j2db.dnd.JSDNDEvent;
import com.servoy.j2db.persistence.Form;
import com.servoy.j2db.persistence.IDataProviderLookup;
import com.servoy.j2db.persistence.IPersist;
import com.servoy.j2db.persistence.ScriptVariable;
import com.servoy.j2db.scripting.IScriptable;
import com.servoy.j2db.scripting.IScriptableProvider;
import com.servoy.j2db.ui.DataRendererOnRenderWrapper;
import com.servoy.j2db.ui.IComponent;
import com.servoy.j2db.ui.IDataRenderer;
import com.servoy.j2db.ui.ISupportOnRenderCallback;
import com.servoy.j2db.ui.ISupportRowBGColorScript;
import com.servoy.j2db.ui.ISupportRowStyling;
import com.servoy.j2db.ui.RenderEventExecutor;
import com.servoy.j2db.ui.runtime.IRuntimeComponent;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.IStyleRule;
import com.servoy.j2db.util.IStyleSheet;
import com.servoy.j2db.util.PersistHelper;
import com.servoy.j2db.util.Utils;
/**
* Panel which corresponds to a part at design-time
* @author jblok
*/
public class DataRenderer extends StyledEnablePanel implements ListCellRenderer, IDataRenderer
{
/*
* _____________________________________________________________ Declaration of attributes
*/
private HashMap<IPersist, IDisplay> fieldComponents;
private boolean usesSliding = false;
private DataAdapterList dataAdapterList;
private boolean isRenderer = false;
private boolean showSelection = false;
private FormController dragNdropController;
private final ISupportOnRenderCallback dataRendererOnRenderWrapper;
/*
* _____________________________________________________________ Declaration and definition of constructors
*/
DataRenderer(IApplication app, boolean showSelection)
{
super(app);
this.showSelection = showSelection;
fieldComponents = new HashMap<IPersist, IDisplay>();
// setInsets(insets);
setOpaque(false);
addMouseListener(new MouseAdapter()
{
@Override
public void mouseReleased(MouseEvent e)
{
getApplication().getFoundSetManager().getEditRecordList().stopEditing(false);
}
//request focus for the form panel
@Override
public void mouseClicked(MouseEvent e)
{
DataRenderer.this.requestFocus();
}
});
dataRendererOnRenderWrapper = new DataRendererOnRenderWrapper(this);
}
public int getYOffset()
{
return yOffset;
}
private int yOffset;
/**
* @param e
*/
private void exportDrag(MouseEvent e)
{
// controller is set when dragNdrop is enabled.
if (dragNdropController != null)
{
boolean isCTRLDown = (e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0;
TransferHandler handler = getTransferHandler();
handler.exportAsDrag(this, e, isCTRLDown ? TransferHandler.COPY : TransferHandler.MOVE);
}
}
public void initDragNDrop(FormController formController, int clientDesignYOffset)
{
if (!GraphicsEnvironment.isHeadless())
{
this.yOffset = clientDesignYOffset;
Form form = formController.getForm();
if (form.getOnDragMethodID() > 0 || form.getOnDragEndMethodID() > 0 || form.getOnDragOverMethodID() > 0 || form.getOnDropMethodID() > 0)
{
this.dragNdropController = formController;
// remove drag&drop from children as it is handled by the data renderer
final DragStartTester dragTester = new DragStartTester();
addMouseMotionListener(dragTester);
addMouseListener(dragTester);
addContainerListener(new ContainerListener()
{
public void componentAdded(ContainerEvent e)
{
Component child = e.getChild();
if (child instanceof JComponent)
{
child.addMouseMotionListener(dragTester);
child.addMouseListener(dragTester);
if (child instanceof ISupportDragNDropTextTransfer) ((ISupportDragNDropTextTransfer)child).clearTransferHandler();
else if (child instanceof DataComboBox) ((DataComboBox)child).disableEditorTransferHandler();
else ((JComponent)child).setTransferHandler(null);
}
}
public void componentRemoved(ContainerEvent e)
{
// ignore
}
});
// Again, needs to negotiate with the draggable object
setTransferHandler(FormDataTransferHandler.getInstance());
new DropTarget(this, (DropTargetListener)FormDataTransferHandler.getInstance());
}
}
}
public void setShowSelection(boolean b)
{
showSelection = b;
// noFocusBorder = BorderFactory.createEmptyBorder(insets.top,insets.left,insets.bottom,insets.right);
// if (insets.top == 0 && insets.left == 0 && insets.bottom == 0 && insets.right == 0)
// {
// focusBorder = noFocusBorder;
// }
// else
// {
// focusBorder = BorderFactory.createMatteBorder(insets.top,insets.left,insets.bottom,insets.right, Color.black);
// }
// setBorder(noFocusBorder);
}
public void addDisplayComponent(IPersist obj, IDisplay comp)
{
fieldComponents.put(obj, comp);
}
@Override
public void setBackground(Color bgColor)
{
if (bgColor != null)
{
super.setBackground(bgColor);
}
setOpaque(!(bgColor == null || bgColor instanceof ColorUIResource));
}
public void destroy()
{
if (dataAdapterList != null)
{
dataAdapterList.destroy();
dataAdapterList = null;
}
Component[] comps = getComponents();
for (Component element : comps)
{
if (element instanceof Applet)
{
((Applet)element).destroy();
}
}
removeAll();
}
/*
* There seesm to be a bug in Swing that cell/list Editor remove couses focus move to fields inside this dataRenderer So we simple block moves during editor
* remove
*/
private boolean traversalPolicyEnabled = true;
@Override
public void removeNotify()
{
try
{
traversalPolicyEnabled = false;
super.removeNotify();
}
finally
{
traversalPolicyEnabled = true;
}
}
public boolean isTraversalPolicyEnabled()
{
return traversalPolicyEnabled;
}
public void notifyVisible(boolean b, List<Runnable> invokeLaterRunnables)
{
if (dataAdapterList != null)
{
//we just forward the call
dataAdapterList.notifyVisible(b, invokeLaterRunnables);
}
Component[] comps = getComponents();
for (Component element : comps)
{
if (element instanceof Applet)
{
if (b)
{
((Applet)element).setVisible(true);
((Applet)element).start();
}
else
{
((Applet)element).stop();
((Applet)element).setVisible(false);
}
}
}
}
private final HashSet<IDisplay> globalFields = new HashSet<IDisplay>();
void createDataAdapter(IApplication app, IDataProviderLookup dataProviderLookup, IScriptExecuter el, ControllerUndoManager undoManager) throws Exception
{
// IScriptExecutor can be null for a design component
FormController formController = el == null ? null : el.getFormController();
dataAdapterList = new DataAdapterList(app, dataProviderLookup, fieldComponents, formController, null, undoManager);
//make it really fields only
HashMap<IPersist, IDisplay> f = new HashMap<IPersist, IDisplay>();
Iterator<Map.Entry<IPersist, IDisplay>> it = fieldComponents.entrySet().iterator();
while (it.hasNext())
{
Map.Entry<IPersist, IDisplay> element = it.next();
if (element.getValue() instanceof IDisplayData)
{
String id = ((IDisplayData)element.getValue()).getDataProviderID();
if (dataProviderLookup.getDataProvider(id) instanceof ScriptVariable)
{
globalFields.add(element.getValue());
}
f.put(element.getKey(), element.getValue());
}
}
fieldComponents = f;
}
public Map<IPersist, IDisplay> getFieldComponents()//used in printing
{
return fieldComponents;
}
public boolean stopUIEditing(boolean looseFocus)
{
if (!isRenderer && dataAdapterList != null) return dataAdapterList.stopUIEditing(looseFocus);
return true;
}
public void setAllNonFieldsEnabled(boolean b)
{
Component[] comp = getComponents();
for (Component c : comp)
{
if (!fieldComponents.containsValue(c))
{
// if (c instanceof IComponent)
// {
// ((IComponent)c).setComponentEnabled(b);
// }
// else
// {
// c.setEnabled(b);
// }
}
}
}
public void setAllNonRowFieldsEnabled(boolean b)
{
Component[] comp = getComponents();
for (Component c : comp)
{
if (!globalFields.contains(c))
{
if (c instanceof IComponent)
{
((IComponent)c).setComponentEnabled(b);
}
else
{
c.setEnabled(b);
}
}
}
}
public DataAdapterList getDataAdapterList()
{
return dataAdapterList;
}
/*
* public Dimension getPreferredSize() { return new Dimension(form.getSize().width, body_end - body_start); //form.getSize(); }
*/
/*
* @see javax.swing.JComponent#paintChildren(java.awt.Graphics)
*/
@Override
protected void paintChildren(Graphics g)
{
if (showSelection && selected)
{
g.setColor(Color.BLACK);
g.fillRect(0, 0, 3, getHeight());
}
try
{
super.paintChildren(g);
}
catch (Exception e)
{
Debug.error(e);
}
}
private String strRowBGColorProvider = null;
private List<Object> rowBGColorArgs = null;
private boolean isRowBGColorCalculation;
// do not use this
public void setRowBGColorProvider(String str, List<Object> args)
{
strRowBGColorProvider = str;
rowBGColorArgs = args;
}
private Color defaultColor = null;
private boolean selected;
public Component getListCellRendererComponent(JList donotusecanbenullifinrecondview, Object value, int index, boolean isSelected, boolean cellHasFocus)
{
return getListCellRendererComponent((JComponent)donotusecanbenullifinrecondview, value, index, isSelected, cellHasFocus);
}
public Component getListCellRendererComponent(JComponent rendererParentCanBeNull, Object value, int index, boolean isSelected, boolean cellHasFocus)
{
this.selected = isSelected;
// if (isSelected)
// {
// setBorder(focusBorder);
// }
// else
// {
// setBorder(noFocusBorder);
// }
boolean bgRowColorSet = false;
// if in Renderer mode for a the List then a renderer can't stop editing.
if (value instanceof IRecordInternal)
{
IRecordInternal val = (IRecordInternal)value;
// if in list view, we need to set the render state for each list item here
if (rendererParentCanBeNull != null)
{
DataAdapterList.setDataRendererComponentsRenderState(this, val);
}
dataAdapterList.setRecord(val, !isRenderer);
// setOpaque(true);
if (index != -1)
{
boolean specialStateCase = (val instanceof PrototypeState || val instanceof FindState);
if (strRowBGColorProvider == null && !specialStateCase)
{
if (rendererParentCanBeNull instanceof ISupportRowBGColorScript)
{
strRowBGColorProvider = ((ISupportRowBGColorScript)rendererParentCanBeNull).getRowBGColorScript();
rowBGColorArgs = ((ISupportRowBGColorScript)rendererParentCanBeNull).getRowBGColorArgs();
}
if (strRowBGColorProvider == null) strRowBGColorProvider = "servoy_row_bgcolor"; //$NON-NLS-1$
isRowBGColorCalculation = val.getRawData().containsCalculation(strRowBGColorProvider);
if (!isRowBGColorCalculation && strRowBGColorProvider.equals("servoy_row_bgcolor"))
{
strRowBGColorProvider = ""; //$NON-NLS-1$
}
defaultColor = getBackground();
}
if (strRowBGColorProvider != null && !"".equals(strRowBGColorProvider)) //$NON-NLS-1$
{
IFoundSetInternal parent = val.getParentFoundSet();
if (parent != null && !specialStateCase)
{
Object bg_color = null;
if (isRowBGColorCalculation)
{
bg_color = parent.getCalculationValue(
val,
strRowBGColorProvider,
Utils.arrayMerge((new Object[] { new Integer(index), new Boolean(isSelected), null, null, Boolean.FALSE }),
Utils.parseJSExpressions(rowBGColorArgs)), null);
}
else
{
try
{
FormController currentForm = dataAdapterList.getFormController();
bg_color = currentForm.executeFunction(strRowBGColorProvider, Utils.arrayMerge((new Object[] { new Integer(index), new Boolean(
isSelected), null, null, currentForm.getName(), val, Boolean.FALSE }), Utils.parseJSExpressions(rowBGColorArgs)), false,
null, true, null);
}
catch (Exception ex)
{
Debug.error(ex);
}
}
if (bg_color != null && !(bg_color.toString().trim().length() == 0) && !(bg_color instanceof Undefined))
{
bgRowColorSet = true;
setBackground(PersistHelper.createColor(bg_color.toString()));
}
else
{
setBackground(defaultColor);
}
}
else
{
setBackground(defaultColor);
}
}
if (rendererParentCanBeNull instanceof ISupportRowStyling && !specialStateCase && !bgRowColorSet)
{
ISupportRowStyling oddEvenStyling = (ISupportRowStyling)rendererParentCanBeNull;
IStyleSheet ss = oddEvenStyling.getRowStyleSheet();
IStyleRule style = isSelected ? oddEvenStyling.getRowSelectedStyle() : null;
if (style != null && style.getAttributeCount() == 0) style = null;
if (style == null)
{
style = (index % 2 == 0) ? oddEvenStyling.getRowOddStyle() : oddEvenStyling.getRowEvenStyle(); // because index = 0 means record = 1
}
if (ss != null && style != null)
{
Color bgColor = ss.getBackground(style);
if (bgColor != null)
{
showSelection = false;
bgRowColorSet = true;
setBackground(bgColor);
}
}
}
}
}
if (rendererParentCanBeNull != null)
{
if (rendererParentCanBeNull.isEnabled() != isEnabled())
{
//Debug.trace(donotusecanbenullifinrecondview.getName()+" "+donotusecanbenullifinrecondview.isEnabled());
setEnabled(rendererParentCanBeNull.isEnabled()); //needed for portals
}
if (bgRowColorSet && !isOpaque())
{
setOpaque(true);
}
dataRendererOnRenderWrapper.getRenderEventExecutor().fireOnRender(hasFocus());
}
// setFont(list.getFont());
//System.out.println(this);
return this;
}
/*
* (non-Javadoc)
*
* @see javax.swing.JComponent#isOpaque()
*/
@Override
public boolean isOpaque()
{
if (getBackground() != null && getBackground().getAlpha() < 255)
{
return false;
}
return super.isOpaque();
}
/*
* (non-Javadoc)
*
* @see javax.swing.JComponent#setOpaque(boolean)
*/
@Override
public void setOpaque(boolean isOpaque)
{
if (getBackground() != null && getBackground().getAlpha() < 255)
{
super.setOpaque(false);
}
else super.setOpaque(isOpaque);
}
/*
* @see JComponent#getToolTipText(MouseEvent)
*/
@Override
public String getToolTipText(MouseEvent event)
{
Component comp = SwingUtilities.getDeepestComponentAt(this, event.getX(), event.getY());
if (comp == null || !(comp instanceof JComponent) || comp == this)
{
return super.getToolTipText(event);
}
Point p = SwingUtilities.convertPoint(this, event.getPoint(), comp);
MouseEvent newEvent = new MouseEvent(comp, event.getID(), event.getWhen(), event.getModifiers(), p.x, p.y, event.getClickCount(),
event.isPopupTrigger());
String tip = ((JComponent)comp).getToolTipText(newEvent);
if (tip != null) return tip;
return getToolTipText();
}
/**
* @return
*/
public boolean isRenderer()
{
return isRenderer;
}
/**
* @param b
*/
public void setRenderer(boolean b)
{
isRenderer = b;
}
/**
* @return
*/
// public Border getFocusBorder()
// {
// return focusBorder;
// }
/**
* @return
*/
// public Border getNoFocusBorder()
// {
// return noFocusBorder;
// }
public boolean isUsingSliding()
{
return usesSliding;
}
public void setUsingSliding(boolean usesSliding)
{
this.usesSliding = usesSliding;
}
public void setComponentEnabled(boolean enabled)
{
setEnabled(enabled);
}
public void setComponentVisible(boolean visible)
{
setVisible(visible);
}
public void refreshRecord(IRecordInternal record)
{
DataAdapterList.setDataRendererComponentsRenderState(this, record);
if (dataAdapterList != null)
{
dataAdapterList.setRecord(record, true);
}
fireDataRendererOnRender();
}
public String getId()
{
return (String)getClientProperty("Id");
}
public Iterator getComponentIterator()
{
return Arrays.asList(getComponents()).iterator();
}
public void add(IComponent c, String name)
{
//ignore
}
public void remove(IComponent c)
{
//ignore
}
private Map componentsUsingSliding;
public void setComponentsUsingSliding(Map componentsUsingSliding)
{
this.componentsUsingSliding = componentsUsingSliding;
}
public Map getComponentsUsingSliding()
{
return componentsUsingSliding;
}
private int spaceToRightMargin;
/**
* Sets the width of the space to keep empty between the right-most component and the right edge of the renderer. This info is only used for growing fields
* that can be limited to fit on the page and setting it will not affect the existing spring layout.
*
* @param spaceToRightMargin the empty space that should be kept by any child to the right margin of the renderer; it is calculated when springs are added
* for printing...
*/
public void setSpaceToRightMargin(int spaceToRightMargin)
{
this.spaceToRightMargin = spaceToRightMargin;
}
/**
* Returns the empty space that should be kept by any child to the right margin of the renderer; it is set when springs are added for printing...
*
* @return the empty space that should be kept by any child to the right margin of the renderer; it is set when springs are added for printing...
*/
public int getSpaceToRightMargin()
{
return spaceToRightMargin;
}
public int onDrag(JSDNDEvent event)
{
Form form = dragNdropController.getForm();
int onDragID = form.getOnDragMethodID();
if (onDragID > 0)
{
Object dragReturn = dragNdropController.executeFunction(Integer.toString(onDragID), new Object[] { event }, false, null, false, "onDragMethodID"); //$NON-NLS-1$
if (dragReturn instanceof Number) return ((Number)dragReturn).intValue();
}
return DRAGNDROP.NONE;
}
public boolean onDragOver(JSDNDEvent event)
{
Form form = dragNdropController.getForm();
int onDragOverID = form.getOnDragOverMethodID();
if (onDragOverID > 0)
{
Object dragOverReturn = dragNdropController.executeFunction(Integer.toString(onDragOverID), new Object[] { event }, false, null, false,
"onDragOverMethodID"); //$NON-NLS-1$
if (dragOverReturn instanceof Boolean) return ((Boolean)dragOverReturn).booleanValue();
}
return form.getOnDropMethodID() > 0;
}
public boolean onDrop(JSDNDEvent event)
{
Form form = dragNdropController.getForm();
int onDropID = form.getOnDropMethodID();
if (onDropID > 0)
{
Object dropHappened = dragNdropController.executeFunction(Integer.toString(onDropID), new Object[] { event }, false, null, false, "onDropMethodID"); //$NON-NLS-1$
if (dropHappened instanceof Boolean) return ((Boolean)dropHappened).booleanValue();
}
return false;
}
public void onDragEnd(JSDNDEvent event)
{
Form form = dragNdropController.getForm();
int onDragEndID = form.getOnDragEndMethodID();
if (onDragEndID > 0)
{
dragNdropController.executeFunction(Integer.toString(onDragEndID), new Object[] { event }, false, null, false, "onDragEndMethodID"); //$NON-NLS-1$
}
}
public IComponent getDragSource(Point xy)
{
Component dragedComp = getComponentAt(xy);
if (!this.equals(dragedComp) && dragedComp instanceof IComponent && dragedComp.isEnabled() && !(dragedComp instanceof SpecialTabPanel) &&
!(dragedComp instanceof SpecialSplitPane)) return (IComponent)dragedComp;
return this;
}
public String getDragFormName()
{
return getDataAdapterList().getFormController().getName();
}
public boolean isGridView()
{
return false;
}
public IRecordInternal getDragRecord(Point xy)
{
return getDataAdapterList().getState();
}
private class DragStartTester extends MouseAdapter implements MouseMotionListener
{
boolean startDrag = false;
@Override
public void mouseMoved(MouseEvent e)
{
}
@Override
public void mouseDragged(MouseEvent e)
{
if (startDrag) exportDrag(SwingUtilities.convertMouseEvent((Component)e.getSource(), e, DataRenderer.this));
startDrag = false;
}
/**
* @see java.awt.event.MouseAdapter#mousePressed(java.awt.event.MouseEvent)
*/
@Override
public void mousePressed(MouseEvent e)
{
startDrag = true;
}
}
/*
* @see com.servoy.j2db.ui.ISupportOnRenderWrapper#getOnRenderComponent()
*/
public ISupportOnRenderCallback getOnRenderComponent()
{
return dataRendererOnRenderWrapper;
}
/*
* @see com.servoy.j2db.ui.ISupportOnRenderWrapper#getOnRenderElementType()
*/
public String getOnRenderElementType()
{
return IRuntimeComponent.FORM;
}
/*
* @see com.servoy.j2db.ui.ISupportOnRenderWrapper#getOnRenderToString()
*/
public String getOnRenderToString()
{
return dataAdapterList != null ? dataAdapterList.getFormController().getForm().toString() : super.toString();
}
private void fireDataRendererOnRender()
{
dataRendererOnRenderWrapper.getRenderEventExecutor().fireOnRender(hasFocus());
@SuppressWarnings("rawtypes")
Iterator compIte = getComponentIterator();
Object comp;
while (compIte.hasNext())
{
comp = compIte.next();
if (comp instanceof IScriptableProvider && !(comp instanceof IDisplayData) && !(comp instanceof IDisplayRelatedData))
{
IScriptable scriptable = ((IScriptableProvider)comp).getScriptObject();
if (scriptable instanceof ISupportOnRenderCallback)
{
RenderEventExecutor rendererEventExecutor = ((ISupportOnRenderCallback)scriptable).getRenderEventExecutor();
boolean hasFocus = (comp instanceof Component) ? ((Component)comp).hasFocus() : false;
rendererEventExecutor.fireOnRender(hasFocus);
}
}
}
}
/*
* @see com.servoy.j2db.dnd.IFormDataDragNDrop#getDragNDropController()
*/
public FormController getDragNDropController()
{
return dragNdropController;
}
}