/*
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.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.geom.Area;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.EventObject;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.TableModelEvent;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import javax.swing.text.JTextComponent;
import javax.swing.text.html.CSS;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.Undefined;
import com.servoy.base.util.ITagResolver;
import com.servoy.j2db.ApplicationException;
import com.servoy.j2db.FormController;
import com.servoy.j2db.IApplication;
import com.servoy.j2db.IView;
import com.servoy.j2db.component.ComponentFormat;
import com.servoy.j2db.component.ServoyBeanState;
import com.servoy.j2db.dataprocessing.DataAdapterList;
import com.servoy.j2db.dataprocessing.DisplaysAdapter;
import com.servoy.j2db.dataprocessing.FindState;
import com.servoy.j2db.dataprocessing.FoundSet;
import com.servoy.j2db.dataprocessing.IDataAdapter;
import com.servoy.j2db.dataprocessing.IDisplay;
import com.servoy.j2db.dataprocessing.IDisplayData;
import com.servoy.j2db.dataprocessing.IDisplayRelatedData;
import com.servoy.j2db.dataprocessing.IEditListener;
import com.servoy.j2db.dataprocessing.IFoundSetInternal;
import com.servoy.j2db.dataprocessing.IRecord;
import com.servoy.j2db.dataprocessing.IRecordInternal;
import com.servoy.j2db.dataprocessing.ISwingFoundSet;
import com.servoy.j2db.dataprocessing.ModificationEvent;
import com.servoy.j2db.dataprocessing.PrototypeState;
import com.servoy.j2db.dataprocessing.Record;
import com.servoy.j2db.dataprocessing.SortColumn;
import com.servoy.j2db.dataprocessing.TagResolver;
import com.servoy.j2db.dataprocessing.ValueFactory.DbIdentValue;
import com.servoy.j2db.dataui.IServoyAwareBean;
import com.servoy.j2db.persistence.PositionComparator;
import com.servoy.j2db.scripting.IScriptable;
import com.servoy.j2db.scripting.IScriptableProvider;
import com.servoy.j2db.smart.J2DBClient;
import com.servoy.j2db.smart.ListView;
import com.servoy.j2db.smart.TableView;
import com.servoy.j2db.ui.IScriptRenderMethods;
import com.servoy.j2db.ui.ISupportCachedLocationAndSize;
import com.servoy.j2db.ui.ISupportOnRenderCallback;
import com.servoy.j2db.ui.ISupportRowStyling;
import com.servoy.j2db.ui.ISupportsDoubleBackground;
import com.servoy.j2db.ui.RenderEventExecutor;
import com.servoy.j2db.ui.RenderableWrapper;
import com.servoy.j2db.ui.runtime.IRuntimeComponent;
import com.servoy.j2db.ui.scripting.IFormatScriptComponent;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.IDelegate;
import com.servoy.j2db.util.IStyleRule;
import com.servoy.j2db.util.IStyleSheet;
import com.servoy.j2db.util.Internalize;
import com.servoy.j2db.util.PersistHelper;
import com.servoy.j2db.util.ScopesUtils;
import com.servoy.j2db.util.ServoyException;
import com.servoy.j2db.util.Text;
import com.servoy.j2db.util.Utils;
import com.servoy.j2db.util.gui.SpecialMatteBorder;
/**
* Tableview renderer/editor wrapper (for use in swing)
* @author jblok
*/
public class CellAdapter extends TableColumn implements TableCellEditor, TableCellRenderer, IDataAdapter, ItemListener, ActionListener, IEditListener
{
private static final long serialVersionUID = 1L;
private static final Object NONE = new Object();
/** row to dataprovider map that holds a temp value to test lazy loading */
private final Set<String> rowAndDataprovider = new HashSet<String>();
//holds list of all displays
private List<CellAdapter> displays;
private final IApplication application;
private final Component renderer;
private final Component editor;
private int editorX;
private int editorWidth;
private final String dataProviderID;
private IRecordInternal currentEditingState;
private DataAdapterList dal;
private final TableView table;
private String name;
private Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
protected static JComponent empty = new JLabel();
// We need a place to store the color the JLabel should be returned
// to after its foreground and background colors have been set
// to the selection background color.
// These ivars will be made protected when their names are finalized.
private Color unselectedForeground;
private Color unselectedBackground;
private Font unselectedFont;
private Color lastEditorBgColor;
private Color lastEditorFgColor;
private Font lastEditorFont;
private Color componentBgColor;
private Color componentFgColor;
private Font componentFont;
private boolean adjusting = false;
private Object lastInvalidValue = NONE;
private RenderableWrapper renderableWrapper;
public CellAdapter(IApplication app, final TableView table, final int index, int width, String name, String title, String dataProviderID,
Component renderer, Component editor)
{
super(index, width);
this.application = app;
this.table = table;
this.dataProviderID = dataProviderID;
this.renderer = renderer;
this.name = name;
if (renderer != null)
{
componentBgColor = renderer.getBackground();
componentFgColor = renderer.getForeground();
componentFont = renderer.getFont();
if (renderer instanceof IScriptableProvider)
{
IScriptable scriptable = ((IScriptableProvider)renderer).getScriptObject();
if (scriptable instanceof ISupportOnRenderCallback)
{
IScriptRenderMethods renderable = ((ISupportOnRenderCallback)scriptable).getRenderable();
if (renderable instanceof RenderableWrapper) renderableWrapper = (RenderableWrapper)renderable;
}
}
renderer.addComponentListener(new ComponentListener()
{
public void componentShown(ComponentEvent e)
{
// if visibility change is done onRender, don't change the column
if (renderableWrapper != null && renderableWrapper.getProperty(RenderableWrapper.PROPERTY_VISIBLE) != null)
{
return;
}
int i = table.getColumnModel().getColumnCount();
table.getColumnModel().addColumn(CellAdapter.this);
// make sure it appears back in it's design position
int realColumnIndex = getRealColumnIndex(table.getColumnModel().getColumns(), index);
if (realColumnIndex < i)
{
table.getColumnModel().moveColumn(i, realColumnIndex);
}
}
private int getRealColumnIndex(Enumeration<TableColumn> columns, int idx)
{
// some other columns (with indexes < index) might be hidden (removed from the table)
// - so in order to correctly position this column we must see the real column index in the
// table where is should appear - based on index and the indexes of other columns (hidden & shown);
// visible columns are still added to the table column model
int realIndex = 0;
while (columns.hasMoreElements())
{
TableColumn ca = columns.nextElement();
if (ca.getModelIndex() < idx)
{
realIndex++;
}
}
return realIndex;
}
public void componentHidden(ComponentEvent e)
{
// if visibility change is done onRender, don't change the column
if (renderableWrapper != null && renderableWrapper.getProperty(RenderableWrapper.PROPERTY_VISIBLE) != null)
{
return;
}
table.getColumnModel().removeColumn(CellAdapter.this);
}
public void componentResized(ComponentEvent e)
{
}
public void componentMoved(ComponentEvent e)
{
}
});
if (renderer instanceof JComponent)
{
Border rBorder = ((JComponent)renderer).getBorder();
noFocusBorder = rBorder;
}
}
this.editor = editor;
if (editor != null)
{
updateEditorX();
updateEditorWidth();
editor.addComponentListener(new ComponentListener()
{
public void componentHidden(ComponentEvent e)
{
}
public void componentMoved(ComponentEvent e)
{
int sourceX = (e.getComponent() instanceof ISupportCachedLocationAndSize)
? ((ISupportCachedLocationAndSize)e.getComponent()).getCachedLocation().x : e.getComponent().getLocation().x;
if (editorX != sourceX) // see also updateEditorX and updateEditorWidth call hierarchy to understand this better
{
onEditorChanged();
editorX = sourceX; // just to make sure
}
}
public void componentResized(ComponentEvent e)
{
int sourceWidth = (e.getComponent() instanceof ISupportCachedLocationAndSize)
? ((ISupportCachedLocationAndSize)e.getComponent()).getCachedSize().width : e.getComponent().getSize().width;
if (editorWidth != sourceWidth) // see also updateEditorX and updateEditorWidth call hierarchy to understand this better
{
onEditorChanged();
editorWidth = sourceWidth; // just to make sure
}
}
public void componentShown(ComponentEvent e)
{
}
});
}
super.setCellRenderer(this);
super.setCellEditor(this);
if (title == null || title.length() == 0)
{
setHeaderValue(" "); //$NON-NLS-1$
}
else
{
setHeaderValue(title);
}
setIdentifier(editor != null ? editor.getName() : name);
if (editor instanceof DataCheckBox)
{
((DataCheckBox)editor).addItemListener(this);
((DataCheckBox)editor).setText(""); //$NON-NLS-1$
((DataCheckBox)renderer).setText(""); //$NON-NLS-1$
}
else if (editor instanceof DataComboBox)
{
((DataComboBox)editor).addItemListener(this);
}
else if (editor instanceof DataCalendar)
{
((DataCalendar)editor).addActionListener(this);
((DataCalendar)editor).addEditListener(this);
}
else if (editor instanceof DataField)
{
((DataField)editor).addFocusListener(new FocusAdapter()
{
@Override
public void focusLost(FocusEvent e)
{
if (!e.isTemporary())
{
application.invokeLater(new Runnable()
{
public void run()
{
// if it already has focus again, igore this.
if (CellAdapter.this.editor.hasFocus()) return;
// if the tableview itself has focus also ignore this
if (CellAdapter.this.table.hasFocus()) return;
// only stop the cell editor if it is still this one.
TableCellEditor tableCellEditor = table.getCellEditor();
if (tableCellEditor == CellAdapter.this) tableCellEditor.stopCellEditing();
}
});
}
}
});
((DataField)editor).addEditListener(this);
}
else if (editor instanceof IDisplayData)
{
((IDisplayData)editor).addEditListener(this);
}
// editor is never transparent
// if the editor must be transparent, then make sure that the getBackGround call done below
// is done on a none transparent field (and reset the transparency)
if (!editor.isOpaque() && editor instanceof JComponent)
{
((JComponent)editor).setOpaque(true);
unselectedBackground = editor.getBackground();
((JComponent)editor).setOpaque(false);
}
else
{
unselectedBackground = editor.getBackground();
}
unselectedForeground = editor.getForeground();
unselectedFont = editor.getFont();
}
public void setDataAdapterList(DataAdapterList dal)
{
this.dal = dal;
if (editor instanceof IDisplayData)
{
((IDisplayData)editor).setTagResolver(dal);
}
}
private boolean findMode = false;
public void setFindMode(boolean b)
{
findMode = b;
if (editor instanceof IDisplayData)
{
IDisplayData dt = (IDisplayData)editor;
if (!(dal.getFormScope() != null && dt.getDataProviderID() != null && dal.getFormScope().get(dt.getDataProviderID()) != Scriptable.NOT_FOUND)) // skip for form variables
{
dt.setValidationEnabled(!b);
}
}
if (renderer instanceof IDisplayData)
{
IDisplayData dt = (IDisplayData)renderer;
if (!(dal.getFormScope() != null && dt.getDataProviderID() != null && dal.getFormScope().get(dt.getDataProviderID()) != Scriptable.NOT_FOUND)) // skip for form variables
{
dt.setValidationEnabled(!b);
}
}
if (displays != null)
{
for (int d = 0; d < displays.size(); d++)
{
displays.get(d).setFindMode(b);
}
}
if (table != null && !table.isEditing()) currentEditingState = null;
}
private boolean isVisible(Component comp)
{
boolean isVisible = false;
if (comp != null)
{
if (comp instanceof IScriptableProvider && ((IScriptableProvider)comp).getScriptObject() instanceof ISupportOnRenderCallback &&
((ISupportOnRenderCallback)((IScriptableProvider)comp).getScriptObject()).getRenderEventExecutor().hasRenderCallback())
{
isVisible = true;
}
else
{
isVisible = comp.isVisible();
}
}
return isVisible;
}
/*
* @see TableCellEditor#getTableCellEditorComponent(JTable, Object, boolean, int, int)
*/
public Component getTableCellEditorComponent(JTable jtable, Object value, boolean isSelected, int row, int column)
{
if (editor == null || !isVisible(editor) || !(jtable.getModel() instanceof IFoundSetInternal))
{
return empty;
}
IRecordInternal newRec = ((IFoundSetInternal)jtable.getModel()).getRecord(row);
if (isSelected)
{
Color bgColor = getBgColor(jtable, isSelected, row, true);
if (bgColor != null && editor instanceof JComponent) ((JComponent)editor).setOpaque(true);
if (bgColor == null)
{
bgColor = unselectedBackground; // unselected background is the default background color of the editor.
}
lastEditorBgColor = bgColor;
if (editor instanceof ISupportsDoubleBackground)
{
((ISupportsDoubleBackground)editor).setBackground(bgColor, unselectedBackground);
}
else
{
editor.setBackground(bgColor);
}
Color fgColor = getFgColor(jtable, isSelected, row);
if (fgColor == null)
{
fgColor = unselectedForeground; // unselected foreground is the default foreground color of the editor.
}
lastEditorFgColor = fgColor;
if (editor instanceof ISupportsDoubleBackground)
{
((ISupportsDoubleBackground)editor).setForeground(fgColor, unselectedForeground);
}
else
{
editor.setForeground(fgColor);
}
Font font = getFont(jtable, isSelected, row);
if (font == null)
{
font = unselectedFont; // unselected font is the default font of the editor.
}
lastEditorFont = font;
editor.setFont(font);
Border styleBorder = getBorder(jtable, isSelected, row);
if (styleBorder != null && editor instanceof JComponent)
{
styleBorder = new ReducedBorder(styleBorder, 0);
if (editor instanceof AbstractButton && !((AbstractButton)editor).isBorderPainted())
{
((AbstractButton)editor).setBorderPainted(true);
}
Border marginBorder = null;
if (noFocusBorder instanceof EmptyBorder)
{
marginBorder = noFocusBorder;
}
else if (noFocusBorder instanceof CompoundBorder && ((CompoundBorder)noFocusBorder).getInsideBorder() instanceof EmptyBorder)
{
marginBorder = ((CompoundBorder)noFocusBorder).getInsideBorder();
}
// if we have margin set on the component, keep it along with the style border
if (marginBorder != null)
{
styleBorder = new CompoundBorder(styleBorder, marginBorder);
}
((JComponent)editor).setBorder(styleBorder);
}
}
// try
// {
// if (currentEditingState != null && newRec != currentEditingState && currentEditingState.isEditing())
// {
// currentEditingState.stopEditing();
// }
// }
// catch (Exception e)
// {
// Debug.error(e);
// }
currentEditingState = newRec;
if (currentEditingState != null)
{
if (editor instanceof IScriptableProvider)
{
IScriptable scriptable = ((IScriptableProvider)editor).getScriptObject();
if (scriptable instanceof ISupportOnRenderCallback)
{
RenderEventExecutor renderEventExecutor = ((ISupportOnRenderCallback)scriptable).getRenderEventExecutor();
if (renderEventExecutor != null && renderEventExecutor.hasRenderCallback())
{
renderEventExecutor.setRenderState(currentEditingState, row, isSelected, true);
}
}
}
// if not enabled or not editable do not start the edit
if (editor instanceof IDisplay && ((IDisplay)editor).isEnabled() && !((IDisplay)editor).isReadOnly())
{
DisplaysAdapter.startEdit(dal, (IDisplay)editor, currentEditingState);
}
if (editor instanceof IDisplayRelatedData)
{
IDisplayRelatedData drd = (IDisplayRelatedData)editor;
IRecordInternal state = ((IFoundSetInternal)jtable.getModel()).getRecord(row);
if (state != null)
{
drd.setRecord(state, true);
}
}
if (editor instanceof IDisplayData) // && dataProviderID != null)
{
try
{
Object data = dal.getValueObject(currentEditingState, dataProviderID);
if (data instanceof DbIdentValue)
{
data = ((DbIdentValue)data).getPkValue();
}
convertAndSetValue(((IDisplayData)editor), data);
}
catch (IllegalArgumentException iae)
{
Debug.error(iae);
}
}
if (editor instanceof IServoyAwareBean)
{
((IServoyAwareBean)editor).setSelectedRecord(new ServoyBeanState(currentEditingState, dal.getFormScope()));
}
if (editor instanceof IScriptableProvider && !(editor instanceof IDisplayData) && !(editor instanceof IDisplayRelatedData))
{
IScriptable scriptable = ((IScriptableProvider)editor).getScriptObject();
if (scriptable instanceof ISupportOnRenderCallback)
{
RenderEventExecutor renderEventExecutor = ((ISupportOnRenderCallback)scriptable).getRenderEventExecutor();
if (renderEventExecutor != null && renderEventExecutor.hasRenderCallback())
{
renderEventExecutor.fireOnRender(editor instanceof JTextComponent ? ((JTextComponent)editor).isEditable() : false);
}
}
}
}
return editor.isVisible() ? editor : empty;
}
private ISwingFoundSet tableViewFoundset = null;
/*
* @see TableCellRenderer#getTableCellRendererComponent(JTable, Object, boolean, boolean, int, int)
*/
public Component getTableCellRendererComponent(JTable jtable, Object value, boolean isSelected, boolean hasFocus, final int row, final int column)
{
if (renderer == null || !isVisible(renderer) || !(jtable.getModel() instanceof IFoundSetInternal))
{
return empty;
}
final ISwingFoundSet foundset = (ISwingFoundSet)jtable.getModel();
if (foundset != tableViewFoundset)
{
// foundset changed
this.tableViewFoundset = foundset;
rowAndDataprovider.clear();
}
final IRecordInternal state;
try
{
state = foundset.getRecord(row);
}
catch (RuntimeException re)
{
Debug.error("Error getting row ", re); //$NON-NLS-1$
return empty;
}
RenderEventExecutor renderEventExecutor = null;
IScriptRenderMethods renderable = null;
if (renderer instanceof IScriptableProvider)
{
IScriptable scriptable = ((IScriptableProvider)renderer).getScriptObject();
if (scriptable instanceof ISupportOnRenderCallback)
{
renderEventExecutor = ((ISupportOnRenderCallback)scriptable).getRenderEventExecutor();
renderable = ((ISupportOnRenderCallback)scriptable).getRenderable();
}
}
Color bgColor = getBgColor(jtable, isSelected, row, false);
if (bgColor != null && renderer instanceof JComponent) ((JComponent)renderer).setOpaque(true);
Color fgColor = getFgColor(jtable, isSelected, row);
Font font = getFont(jtable, isSelected, row);
// set the sizes of the to render component also in the editor if the editor is not used.
// so that getLocation and getWidth in scripting on tableviews do work.
if (editor != null && editor.getParent() == null)
{
Rectangle cellRect = jtable.getCellRect(row, column, false);
editor.setLocation(cellRect.x, cellRect.y);
editor.setSize(cellRect.width, cellRect.height);
}
boolean isRenderWithOnRender = renderEventExecutor != null && renderEventExecutor.hasRenderCallback() && renderable instanceof RenderableWrapper;
Color renderBgColor = null;
if (isSelected)
{
if (!isRenderWithOnRender || renderEventExecutor.isDifferentRenderState(state, row, isSelected))
{
Color tableSelectionColor = jtable.getSelectionForeground();
if (bgColor != null)
{
tableSelectionColor = adjustColorDifference(bgColor, tableSelectionColor);
}
((RenderableWrapper)renderable).clearProperty(RenderableWrapper.PROPERTY_FGCOLOR);
renderer.setForeground(fgColor != null ? fgColor : tableSelectionColor);
((RenderableWrapper)renderable).clearProperty(RenderableWrapper.PROPERTY_BGCOLOR);
renderBgColor = (bgColor != null ? bgColor : jtable.getSelectionBackground());
renderer.setBackground(renderBgColor);
if (font != null)
{
((RenderableWrapper)renderable).clearProperty(RenderableWrapper.PROPERTY_FONT);
renderer.setFont(font);
}
}
else if (isRenderWithOnRender && foundset.getSize() == 1)
{
// if the foundset contains a single record, we need to force trigger onRender
// because renderEventExecutor refers already to the changed render state
renderEventExecutor.setRenderStateChanged();
}
}
else
{
if (isRenderWithOnRender)
{
if (renderEventExecutor.isDifferentRenderState(state, row, isSelected))
{
Color newBGColor = bgColor != null ? bgColor : componentBgColor;
if (newBGColor != null)
{
((RenderableWrapper)renderable).clearProperty(RenderableWrapper.PROPERTY_BGCOLOR);
renderBgColor = newBGColor;
renderer.setBackground(renderBgColor);
}
Color newFGColor = fgColor != null ? fgColor : componentFgColor;
if (newFGColor != null)
{
((RenderableWrapper)renderable).clearProperty(RenderableWrapper.PROPERTY_FGCOLOR);
renderer.setForeground(newFGColor);
}
else if (newBGColor != null)
{
renderer.setForeground(adjustColorDifference(newBGColor, jtable.getForeground()));
}
Font newFont = font != null ? font : componentFont;
if (newFont != null)
{
((RenderableWrapper)renderable).clearProperty(RenderableWrapper.PROPERTY_FONT);
renderer.setFont(newFont);
}
}
}
else
{
// now get the editors background. if we don't do that then scripting doesn't show up
Color background = editor.getBackground();
if (background != null && !background.equals(lastEditorBgColor))
{
unselectedBackground = background;
lastEditorBgColor = background;
}
Color foreground = editor.getForeground();
if (foreground != null && !foreground.equals(lastEditorFgColor))
{
unselectedForeground = foreground;
}
Font editorFont = editor.getFont();
if (editorFont != null && !editorFont.equals(lastEditorFont))
{
unselectedFont = editorFont;
}
if (editor instanceof IDisplayData && ((IDisplayData)editor).isValueValid() || !(editor instanceof IDisplayData))
{
Color currentForeground = (fgColor != null ? fgColor : (unselectedForeground != null) ? unselectedForeground : jtable.getForeground());
renderer.setForeground(currentForeground);
}
Color currentColor = (bgColor != null ? bgColor : (unselectedBackground != null) ? unselectedBackground : jtable.getBackground());
renderer.setBackground(currentColor);
Font currentFont = (font != null ? font : (unselectedFont != null) ? unselectedFont : jtable.getFont());
renderer.setFont(currentFont);
}
}
if (renderer instanceof JComponent)
{
applyRowBorder((JComponent)renderer, jtable, isSelected, row, hasFocus);
}
boolean printing = Utils.getAsBoolean(application.getRuntimeProperties().get("isPrinting")); //$NON-NLS-1$
if (renderEventExecutor != null && renderEventExecutor.hasRenderCallback())
{
renderEventExecutor.setRenderState(state, row, isSelected, true);
}
if (renderer instanceof IDisplayRelatedData)
{
IDisplayRelatedData drd = (IDisplayRelatedData)renderer;
String relationName = drd.getSelectedRelationName();
if (state != null)
{
if (relationName != null)
{
if (!printing && !state.isRelatedFoundSetLoaded(relationName, null))
{
IApplication app = dal.getApplication();
((IDisplayData)renderer).setValueObject(null);
String key = row + "_" + relationName + "_" + null; //$NON-NLS-1$ //$NON-NLS-2$
if (!rowAndDataprovider.contains(key))
{
rowAndDataprovider.add(key);
Runnable r = new ASynchonizedCellLoad(app, jtable, foundset, row, jtable.convertColumnIndexToModel(column), relationName,
drd.getDefaultSort(), null);
app.getScheduledExecutor().execute(r);
}
return renderer.isVisible() ? renderer : empty;
}
}
drd.setRecord(state, true);
}
}
if (renderer instanceof IDisplayData)
{
if (state != null)
{
Object data = null;
if (dataProviderID != null)
{
int index = -1;
if (!printing && (index = dataProviderID.indexOf('.')) > 0)
{
if (!ScopesUtils.isVariableScope(dataProviderID))
{
String partName = dataProviderID.substring(0, index);
final String restName = dataProviderID.substring(index + 1);
String relationName = partName;
if (relationName != null && !state.isRelatedFoundSetLoaded(relationName, restName))
{
IApplication app = dal.getApplication();
((IDisplayData)renderer).setValueObject(null);
String key = row + "_" + relationName + "_" + restName; //$NON-NLS-1$ //$NON-NLS-2$
if (!rowAndDataprovider.contains(key))
{
rowAndDataprovider.add(key);
List<SortColumn> defaultPKSortColumns = null;
try
{
defaultPKSortColumns = app.getFoundSetManager().getDefaultPKSortColumns(
app.getFlattenedSolution().getRelation(relationName).getForeignDataSource());
}
catch (ServoyException e)
{
Debug.error(e);
}
Runnable r = new ASynchonizedCellLoad(app, jtable, foundset, row, jtable.convertColumnIndexToModel(column), relationName,
defaultPKSortColumns, restName);
app.getScheduledExecutor().execute(r);
}
return renderer.isVisible() ? renderer : empty;
}
IFoundSetInternal rfs = state.getRelatedFoundSet(relationName);
if (rfs != null)
{
int selected = rfs.getSelectedIndex();
// in printing selected row will be -1, but aggregates
// should still go through record 0
if (selected == -1 && rfs.getSize() > 0)
{
selected = 0;
}
final IRecordInternal relState = rfs.getRecord(selected);
if (testCalc(restName, relState, row, jtable.convertColumnIndexToModel(column), foundset)) return renderer;
}
}
}
if (!((IDisplayData)renderer).needEntireState() && !printing &&
testCalc(dataProviderID, state, row, jtable.convertColumnIndexToModel(column), foundset))
{
return renderer;
}
try
{
data = dal.getValueObject(state, dataProviderID);
}
catch (IllegalArgumentException iae)
{
Debug.error(iae);
data = "<conversion error>"; //$NON-NLS-1$
}
}
((IDisplayData)renderer).setTagResolver(new ITagResolver()
{
public String getStringValue(String nm)
{
return TagResolver.formatObject(dal.getValueObject(state, nm), application.getLocale(), dal.getApplication().getSettings());
}
});
if (data instanceof DbIdentValue)
{
data = ((DbIdentValue)data).getPkValue();
}
convertAndSetValue(((IDisplayData)renderer), data);
}
}
if (renderer instanceof IServoyAwareBean && state != null)
{
((IServoyAwareBean)renderer).setSelectedRecord(new ServoyBeanState(state, dal.getFormScope()));
}
if (!(renderer instanceof IDisplayData) && !(renderer instanceof IDisplayRelatedData) && renderEventExecutor != null &&
renderEventExecutor.hasRenderCallback())
{
renderEventExecutor.fireOnRender(false);
}
// when enabled state is changed during onRender, the bgcolor is cleared; make sure we have that set again
// if the bgcolor is not changed during onRender
if (isRenderWithOnRender && renderBgColor != null && ((RenderableWrapper)renderable).getProperty(RenderableWrapper.PROPERTY_BGCOLOR) == null)
{
renderer.setBackground(renderBgColor);
}
return renderer.isVisible() ? renderer : empty;
}
/**
* Ensures that the background color difference in background color and foreground color is at least "half" of the color range
* @param bgColor
* @param foregroundColor
* @return
*/
private Color adjustColorDifference(Color bgColor, Color foregroundColor)
{
int red = Math.abs(foregroundColor.getRed() - bgColor.getRed());
int blue = Math.abs(foregroundColor.getBlue() - bgColor.getBlue());
int green = Math.abs(foregroundColor.getGreen() - bgColor.getBlue());
if (red < 128 && blue < 128 && green < 128)
{
red = Math.abs(foregroundColor.getRed() - 255);
blue = Math.abs(foregroundColor.getBlue() - 255);
green = Math.abs(foregroundColor.getGreen() - 255);
return Internalize.intern(new Color(red, blue, green));
}
return foregroundColor;
}
private Object getStyleAttributeForRow(JTable jtable, boolean isSelected, int row, ISupportRowStyling.ATTRIBUTE rowStyleAttribute)
{
Object rowStyleAttrValue = null;
IRecordInternal state = ((IFoundSetInternal)jtable.getModel()).getRecord(row);
boolean specialStateCase = (state instanceof PrototypeState || state instanceof FindState);
if (/* !(renderer instanceof JButton) && */!specialStateCase)
{
if (jtable instanceof ISupportRowStyling)
{
ISupportRowStyling oddEvenStyling = (ISupportRowStyling)jtable;
IStyleSheet ss = oddEvenStyling.getRowStyleSheet();
IStyleRule style = isSelected ? oddEvenStyling.getRowSelectedStyle() : null;
if (style != null && style.getAttributeCount() == 0) style = null;
if (style == null)
{
style = (row % 2 == 0) ? oddEvenStyling.getRowOddStyle() : oddEvenStyling.getRowEvenStyle(); // because index = 0 means record = 1
}
rowStyleAttrValue = getStyleAttribute(ss, style, rowStyleAttribute);
}
}
return rowStyleAttrValue;
}
private Object getStyleAttribute(IStyleSheet styleSheet, IStyleRule style, ISupportRowStyling.ATTRIBUTE styleAttribute)
{
if (styleSheet != null && style != null)
{
switch (styleAttribute)
{
case BGCOLOR :
return style.getValue(CSS.Attribute.BACKGROUND_COLOR.toString()) != null ? styleSheet.getBackground(style) : null;
case FGCOLOR :
return style.getValue(CSS.Attribute.COLOR.toString()) != null ? styleSheet.getForeground(style) : null;
case FONT :
return styleSheet.hasFont(style) ? styleSheet.getFont(style) : null;
case BORDER :
return styleSheet.hasBorder(style) ? styleSheet.getBorder(style) : null;
}
}
return null;
}
public Color getHeaderBgColor(ISupportRowStyling rowStyling)
{
return (Color)getStyleAttribute(rowStyling.getRowStyleSheet(), rowStyling.getHeaderStyle(), ISupportRowStyling.ATTRIBUTE.BGCOLOR);
}
public Color getHeaderFgColor(ISupportRowStyling rowStyling)
{
return (Color)getStyleAttribute(rowStyling.getRowStyleSheet(), rowStyling.getHeaderStyle(), ISupportRowStyling.ATTRIBUTE.FGCOLOR);
}
public Font getHeaderFont(ISupportRowStyling rowStyling)
{
return (Font)getStyleAttribute(rowStyling.getRowStyleSheet(), rowStyling.getHeaderStyle(), ISupportRowStyling.ATTRIBUTE.FONT);
}
public Border getHeaderBorder(ISupportRowStyling rowStyling)
{
return (Border)getStyleAttribute(rowStyling.getRowStyleSheet(), rowStyling.getHeaderStyle(), ISupportRowStyling.ATTRIBUTE.BORDER);
}
private Color getFgColor(JTable jtable, boolean isSelected, int row)
{
return (Color)getStyleAttributeForRow(jtable, isSelected, row, ISupportRowStyling.ATTRIBUTE.FGCOLOR);
}
private Font getFont(JTable jtable, boolean isSelected, int row)
{
return (Font)getStyleAttributeForRow(jtable, isSelected, row, ISupportRowStyling.ATTRIBUTE.FONT);
}
private Border getBorder(JTable jtable, boolean isSelected, int row)
{
return (Border)getStyleAttributeForRow(jtable, isSelected, row, ISupportRowStyling.ATTRIBUTE.BORDER);
}
private Color getBgColor(JTable jtable, boolean isSelected, int row, boolean isEdited)
{
Color bgColor = null;
IRecordInternal state = ((IFoundSetInternal)jtable.getModel()).getRecord(row);
boolean specialStateCase = (state instanceof PrototypeState || state instanceof FindState || state == null || state.getRawData() == null);
if (/* !(renderer instanceof JButton) && */!specialStateCase)
{
ISwingFoundSet foundset = (ISwingFoundSet)jtable.getModel();
bgColor = (Color)getStyleAttributeForRow(jtable, isSelected, row, ISupportRowStyling.ATTRIBUTE.BGCOLOR);
String strRowBGColorProvider = null;
List<Object> rowBGColorArgs = null;
if (jtable instanceof IView)
{
strRowBGColorProvider = ((IView)jtable).getRowBGColorScript();
rowBGColorArgs = ((IView)jtable).getRowBGColorArgs();
}
if (strRowBGColorProvider == null) strRowBGColorProvider = "servoy_row_bgcolor"; //$NON-NLS-1$
boolean isRowBGColorCalculation = state.getRawData().containsCalculation(strRowBGColorProvider);
if (!isRowBGColorCalculation && strRowBGColorProvider.equals("servoy_row_bgcolor")) //$NON-NLS-1$
{
strRowBGColorProvider = ""; //$NON-NLS-1$
}
if (strRowBGColorProvider != null && !"".equals(strRowBGColorProvider)) //$NON-NLS-1$
{
Object bg_color = null;
// TODO this should be done better....
Record.VALIDATE_CALCS.set(Boolean.FALSE);
try
{
String type = (editor instanceof IScriptableProvider && ((IScriptableProvider)editor).getScriptObject() instanceof IRuntimeComponent)
? ((IRuntimeComponent)((IScriptableProvider)editor).getScriptObject()).getElementType() : null;
String nm = (editor instanceof IDisplayData) ? ((IDisplayData)editor).getDataProviderID() : null;
if (isRowBGColorCalculation)
{
bg_color = foundset.getCalculationValue(
state,
strRowBGColorProvider,
Utils.arrayMerge((new Object[] { new Integer(row), new Boolean(isSelected), type, nm, new Boolean(isEdited) }),
Utils.parseJSExpressions(rowBGColorArgs)), null);
}
else
{
try
{
FormController currentForm = dal.getFormController();
bg_color = currentForm.executeFunction(strRowBGColorProvider, Utils.arrayMerge((new Object[] { new Integer(row), new Boolean(
isSelected), type, nm, currentForm.getName(), state, new Boolean(isEdited) }), Utils.parseJSExpressions(rowBGColorArgs)),
false, null, true, null);
}
catch (Exception ex)
{
Debug.error(ex);
}
}
}
finally
{
Record.VALIDATE_CALCS.set(null);
}
if (bg_color != null && !(bg_color.toString().trim().length() == 0) && !(bg_color instanceof Undefined))
{
bgColor = PersistHelper.createColor(bg_color.toString());
}
}
}
return bgColor;
}
private void applyRowBorder(JComponent component, JTable jtable, boolean isSelected, int row, boolean hasFocus)
{
Border styleBorder = getBorder(jtable, isSelected, row);
if (styleBorder != null)
{
styleBorder = new ReducedBorder(styleBorder, 0);
if (component instanceof AbstractButton && !((AbstractButton)component).isBorderPainted())
{
((AbstractButton)component).setBorderPainted(true);
}
}
else
{
styleBorder = noFocusBorder;
}
Border marginBorder = null;
if (noFocusBorder instanceof EmptyBorder)
{
marginBorder = noFocusBorder;
}
else if (noFocusBorder instanceof CompoundBorder && ((CompoundBorder)noFocusBorder).getInsideBorder() instanceof EmptyBorder)
{
marginBorder = ((CompoundBorder)noFocusBorder).getInsideBorder();
}
Border adjustedBorder = null;
if (!hasFocus)
{
if (styleBorder instanceof ReducedBorder && marginBorder != null)
{
styleBorder = new CompoundBorder(styleBorder, marginBorder);
}
adjustedBorder = styleBorder;
}
else
{
adjustedBorder = UIManager.getBorder("Table.focusCellHighlightBorder"); //$NON-NLS-1$
if (styleBorder != null)
{
if (styleBorder instanceof ReducedBorder)
{
if (marginBorder != null)
{
adjustedBorder = new CompoundBorder(styleBorder, new CompoundBorder(adjustedBorder, marginBorder));
}
else
{
adjustedBorder = new CompoundBorder(styleBorder, adjustedBorder);
}
}
else if (styleBorder instanceof CompoundBorder)
{
Border insideBorder = ((CompoundBorder)styleBorder).getInsideBorder();
Border outsideBorder = ((CompoundBorder)styleBorder).getOutsideBorder();
if (outsideBorder instanceof SpecialMatteBorder) adjustedBorder = new CompoundBorder(adjustedBorder, styleBorder);
else adjustedBorder = new CompoundBorder(adjustedBorder, insideBorder);
}
else if (styleBorder instanceof SpecialMatteBorder)
{
adjustedBorder = new CompoundBorder(adjustedBorder, styleBorder);
}
else
{
// keep the renderer content at the same position,
// create a focus border with the same border insets
Insets noFocusBorderInsets = styleBorder.getBorderInsets(component);
Insets adjustedBorderInsets = adjustedBorder.getBorderInsets(component);
EmptyBorder emptyInsideBorder = new EmptyBorder(Math.max(0, noFocusBorderInsets.top - adjustedBorderInsets.top), Math.max(0,
noFocusBorderInsets.left - adjustedBorderInsets.left), Math.max(0, noFocusBorderInsets.bottom - adjustedBorderInsets.bottom), Math.max(
0, noFocusBorderInsets.right - adjustedBorderInsets.right));
adjustedBorder = new CompoundBorder(adjustedBorder, emptyInsideBorder);
}
}
}
component.setBorder(adjustedBorder);
}
private boolean testCalc(final String possibleCalcDataprovider, final IRecordInternal state, final int row, final int column, final ISwingFoundSet foundset)
{
if (state != null && !(state instanceof PrototypeState || state instanceof FindState) &&
state.getRawData().containsCalculation(possibleCalcDataprovider) && state.getRawData().mustRecalculate(possibleCalcDataprovider, true))
{
IApplication app = dal.getApplication();
convertAndSetValue(((IDisplayData)renderer), state.getRawData().getValue(possibleCalcDataprovider));
final String key = row + "_" + possibleCalcDataprovider; //$NON-NLS-1$
if (!rowAndDataprovider.contains(key))
{
rowAndDataprovider.add(key);
app.getScheduledExecutor().execute(new Runnable()
{
public void run()
{
state.getValue(possibleCalcDataprovider);
application.invokeLater(new Runnable()
{
public void run()
{
rowAndDataprovider.remove(key);
foundset.fireTableModelEvent(row, row, column, TableModelEvent.UPDATE);
Container parent = table.getParent();
while (parent != null && !(parent instanceof ListView))
{
parent = parent.getParent();
}
if (parent instanceof ListView)
{
((ListView)parent).repaint();
}
}
});
}
});
}
return true;
}
return false;
}
@Override
public Object getHeaderValue()
{
Object value = super.getHeaderValue();
return Text.processTags((String)value, dal);
}
class ASynchonizedCellLoad implements Runnable
{
private final IApplication app;
private final ISwingFoundSet foundset;
private final int row;
private final int column;
private final String relationName;
private final List<SortColumn> sort;
private final String restName;
private final JTable jtable;
ASynchonizedCellLoad(IApplication app, JTable jtable, ISwingFoundSet foundset, int row, int column, String relationName, List<SortColumn> sort,
String restName)
{
this.app = app;
this.foundset = foundset;
this.row = row;
this.column = column;
this.relationName = relationName;
this.sort = sort;
this.restName = restName;
this.jtable = jtable;
}
public void run()
{
try
{
final IRecordInternal state = foundset.getRecord(row);
if (state != null)
{
// only retrieve
if (!((J2DBClient)app).isConnected())
{
if (Debug.tracing())
{
Debug.trace("Client not connected, rescheduling it with a timeout of 5 seconds, clientid: " + app.getClientID()); //$NON-NLS-1$
}
app.getScheduledExecutor().schedule(this, 5, TimeUnit.SECONDS);
}
else
{
IFoundSetInternal fs = state.getRelatedFoundSet(relationName, sort);
// this triggers an update of related foundset if mustQueryForUpdates is true
// needed when foundset is not touched but still needs to be up to date
if (fs != null) fs.getSize();
if (fs == null && !((J2DBClient)app).isConnected())
{
if (Debug.tracing())
{
Debug.trace("Client got disconnected, rescheduling it with a timeout of 5 seconds, clientid: " + app.getClientID()); //$NON-NLS-1$
}
app.getScheduledExecutor().schedule(this, 5, TimeUnit.SECONDS);
}
else
{
if (fs != null && restName != null)
{
fs.getDataProviderValue(restName);// only do lookup for aggregate
}
app.invokeLater(new Runnable()
{
public void run()
{
rowAndDataprovider.remove(row + "_" + relationName + "_" + restName); //$NON-NLS-1$ //$NON-NLS-2$
foundset.fireTableModelEvent(row, row, column, TableModelEvent.UPDATE);
Container parent = jtable.getParent();
while (parent != null && !(parent instanceof ListView))
{
parent = parent.getParent();
}
if (parent instanceof ListView)
{
((ListView)parent).repaint();
}
}
});
}
}
}
}
catch (RuntimeException re)
{
if (Debug.tracing())
{
Debug.trace("Exception in asyn load, rescheduling it with a timeout of 5 seconds, clientid: " + app.getClientID()); //$NON-NLS-1$
}
app.getScheduledExecutor().schedule(this, 5, TimeUnit.SECONDS);
}
}
}
/*
* @see IDataAdapter#setState(State)
*/
public void setRecord(IRecordInternal state)
{
// this is called but never do anything with this value, the renderer and
// editer have to lookup there values them selfs
}
/*
* @see IDataAdapter#getDataProviderID()
*/
public String getDataProviderID()
{
return dataProviderID;
}
/*
* _____________________________________________________________ DataListener
*/
private final List<IDataAdapter> listeners = new ArrayList<IDataAdapter>();
public void addDataListener(IDataAdapter l)
{
if (!listeners.contains(l) && l != this) listeners.add(l);
}
public void removeDataListener(IDataAdapter dataListener)
{
listeners.remove(dataListener);
}
private void fireModificationEvent(IRecord record)
{
// Also notify the table about changes, there seems no other way to do this...
TableModel parent = table.getModel();
if (parent instanceof ISwingFoundSet)
{
int index = ((ISwingFoundSet)parent).getRecordIndex(record);
if (index != -1) ((ISwingFoundSet)parent).fireTableModelEvent(index, index, TableModelEvent.ALL_COLUMNS, TableModelEvent.UPDATE);
}
}
private boolean gettingEditorValue = false;
public Object getCellEditorValue()
{
// test if currentEditing state isn't deleted already
if (currentEditingState == null || dataProviderID == null || (currentEditingState != null && currentEditingState.getParentFoundSet() == null)) return null;
Object comp = editor;
if ((comp instanceof IDisplay && ((IDisplay)comp).isReadOnly()) || gettingEditorValue)
{
return currentEditingState.getValue(getDataProviderID());
}
try
{
gettingEditorValue = true;
if (comp instanceof IDelegate< ? >)
{
comp = ((IDelegate< ? >)comp).getDelegate();
}
//HACK:needed for commit value copied from other hack 'processfocus' in DataField
if (comp instanceof DataField && ((DataField)comp).isEditable())
{
DataField edit = (DataField)comp;
boolean needEntireState = edit.needEntireState();
try
{
edit.setNeedEntireState(false);
int fb = edit.getFocusLostBehavior();
if (fb == JFormattedTextField.COMMIT || fb == JFormattedTextField.COMMIT_OR_REVERT)
{
try
{
edit.commitEdit();
// Give it a chance to reformat.
edit.setValueObject(edit.getValue());
}
catch (ParseException pe)
{
return null;
}
}
else if (fb == JFormattedTextField.REVERT)
{
edit.setValueObject(edit.getValue());
}
}
finally
{
edit.setNeedEntireState(needEntireState);
}
}
Object obj = null;
if (editor instanceof IDisplayData)
{
IDisplayData displayData = (IDisplayData)editor;
obj = Utils.removeJavascripLinkFromDisplay(displayData, null);
if (!findMode)
{
// use UI converter to convert from UI value to record value
obj = ComponentFormat.applyUIConverterFromObject(displayData, obj, dataProviderID, application.getFoundSetManager());
}
// if the editor is not enable or is readonly dont try to set any value.
if (!displayData.isEnabled() || displayData.isReadOnly()) return obj;
// then make sure the current state is in edit, if not, try to start it else just return.
// this can happen when toggeling with readonly. case 233226 or 232188
if (!currentEditingState.isEditing() && !currentEditingState.startEditing()) return obj;
try
{
if (currentEditingState != null && (obj == null || "".equals(obj)) && currentEditingState.getValue(dataProviderID) == null) //$NON-NLS-1$
{
return null;
}
}
catch (IllegalArgumentException iae)
{
Debug.error(iae);
}
Object oldVal = null;
if (currentEditingState instanceof FindState)
{
if (displayData instanceof IScriptableProvider && ((IScriptableProvider)displayData).getScriptObject() instanceof IFormatScriptComponent &&
((IFormatScriptComponent)((IScriptableProvider)displayData).getScriptObject()).getComponentFormat() != null)
{
((FindState)currentEditingState).setFormat(dataProviderID,
((IFormatScriptComponent)((IScriptableProvider)displayData).getScriptObject()).getComponentFormat().parsedFormat);
}
try
{
oldVal = currentEditingState.getValue(dataProviderID);
}
catch (IllegalArgumentException iae)
{
Debug.error("Error getting the previous value", iae); //$NON-NLS-1$
oldVal = null;
}
currentEditingState.setValue(dataProviderID, obj);
if (!Utils.equalObjects(oldVal, obj))
{
// call notifyLastNewValue changed so that the onChangeEvent will be fired and called when attached.
displayData.notifyLastNewValueWasChange(oldVal, obj);
obj = dal.getValueObject(currentEditingState, dataProviderID);
}
}
else
{
if (!displayData.isValueValid() && Utils.equalObjects(lastInvalidValue, obj))
{
// already validated
return obj;
}
try
{
adjusting = true;
try
{
oldVal = currentEditingState.getValue(dataProviderID);
}
catch (IllegalArgumentException iae)
{
Debug.error("Error getting the previous value", iae); //$NON-NLS-1$
}
try
{
if (oldVal == Scriptable.NOT_FOUND && dal.getFormScope().has(dataProviderID, dal.getFormScope()))
{
oldVal = dal.getFormScope().get(dataProviderID);
dal.getFormScope().put(dataProviderID, obj);
IFoundSetInternal foundset = currentEditingState.getParentFoundSet();
if (foundset instanceof FoundSet) ((FoundSet)foundset).fireFoundSetChanged();
}
else currentEditingState.setValue(dataProviderID, obj);
}
catch (IllegalArgumentException e)
{
Debug.trace(e);
displayData.setValueValid(false, oldVal);
application.handleException(null, new ApplicationException(ServoyException.INVALID_INPUT, e));
Object stateValue = null;
try
{
stateValue = dal.getValueObject(currentEditingState, dataProviderID);
}
catch (IllegalArgumentException iae)
{
Debug.error(iae);
}
Object displayValue;
if (Utils.equalObjects(oldVal, stateValue))
{
// reset display to typed value
displayValue = obj;
}
else
{
// reset display to changed value in validator method
displayValue = stateValue;
}
convertAndSetValue(displayData, displayValue);
return displayValue;
}
if (!Utils.equalObjects(oldVal, obj))
{
fireModificationEvent(currentEditingState);
displayData.notifyLastNewValueWasChange(oldVal, obj);
obj = dal.getValueObject(currentEditingState, dataProviderID);
convertAndSetValue(displayData, obj);// we also want to reset the value in the current display if changed by script
}
else if (!displayData.isValueValid())
{
displayData.notifyLastNewValueWasChange(null, obj);
}
else
{
displayData.setValueValid(true, null);
}
}
finally
{
adjusting = false;
if (displayData.isValueValid())
{
lastInvalidValue = NONE;
}
else
{
lastInvalidValue = obj;
}
}
}
}
return obj;
}
finally
{
gettingEditorValue = false;
}
}
/**
* @param obj
* @param displayData
*/
public void convertAndSetValue(IDisplayData displayData, Object obj)
{
if (!findMode)
{
// use UI converter to convert from UI value to record value
displayData.setValueObject(ComponentFormat.applyUIConverterToObject(displayData, obj, dataProviderID, application.getFoundSetManager()));
}
else
{
displayData.setValueObject(obj);
}
}
/*
* @see CellEditor#isCellEditable(EventObject)
*/
public boolean isCellEditable(EventObject anEvent)
{
if (editor.isEnabled() || hasOnRender(editor))
{
// if we enable this the onAction is not fired and it is not possible
// to copy text from non editable field
// if (editor instanceof JTextComponent)
// {
// return ((JTextComponent)editor).isEditable();
// }
return true;
}
return false;
}
private boolean hasOnRender(Component c)
{
if (c instanceof IScriptableProvider)
{
IScriptable scriptable = ((IScriptableProvider)c).getScriptObject();
if (scriptable instanceof ISupportOnRenderCallback)
{
RenderEventExecutor renderEventExecutor = ((ISupportOnRenderCallback)scriptable).getRenderEventExecutor();
return renderEventExecutor != null && renderEventExecutor.hasRenderCallback();
}
}
return false;
}
/*
* @see CellEditor#shouldSelectCell(EventObject)
*/
public boolean shouldSelectCell(EventObject anEvent)
{
return true;
}
private boolean isStopping = false;// we are not completly following the swing model which can couse repetive calls by jtable due to fire of tableContentChange
public boolean stopCellEditing()
{
if (!isStopping)
{
try
{
isStopping = true;
// Get the current celleditor value for testing notify changed.
getCellEditorValue();
// if the notify changed failed the mustTestLastValue is set and we shouldn't allow stopping:
if (editor instanceof IDisplayData && !((IDisplayData)editor).isValueValid())
{
return false;
}
CellEditorListener l = listener;
if (l != null)
{
// TODO this also triggers (or can trigger) a getCellEditorValue() call.. (so the vallue is set in the record, conversion/validation is called again)
l.editingStopped(new ChangeEvent(this));
}
return true;
}
finally
{
isStopping = false;
}
}
else
{
return true;
}
}
/*
* @see CellEditor#cancelCellEditing()
*/
public void cancelCellEditing()
{
if (listener != null) listener.editingCanceled(new ChangeEvent(this));
}
private CellEditorListener listener = null; // allow only one
/*
* @see CellEditor#addCellEditorListener(CellEditorListener)
*/
public void addCellEditorListener(CellEditorListener l)
{
listener = l;
}
/*
* @see CellEditor#removeCellEditorListener(CellEditorListener)
*/
public void removeCellEditorListener(CellEditorListener l)
{
listener = null;
}
public void displayValueChanged(ModificationEvent event)
{
valueChanged(event);
}
/*
* @see JSModificationListener#valueChanged(ModificationEvent)
*/
public void valueChanged(ModificationEvent e)
{
if (adjusting) return;
try
{
adjusting = true;
// ignore globals in a cell adapter, will be handled by the row manager
if (!table.isEditing() && ScopesUtils.isVariableScope(e.getName()))
{
// test if it is a related
if (dataProviderID != null && dataProviderID.indexOf('.') != -1)
{
TableModel parent = table.getModel();
if (parent instanceof ISwingFoundSet)
{
// it could be based on that global so fire a table event.
((ISwingFoundSet)parent).fireTableModelEvent(0, parent.getRowCount() - 1, TableModelEvent.ALL_COLUMNS, TableModelEvent.UPDATE);
}
}
return;
}
// refresh value
IRecord s = e.getRecord();
if (s == null)
{
TableModel tm = table.getModel();
if (tm instanceof IFoundSetInternal)
{
IFoundSetInternal fs = (IFoundSetInternal)tm;
int selRow = fs.getSelectedIndex();
if (selRow != -1)
{
s = fs.getRecord(selRow);
}
}
}
if (s != null)
{
Object obj = e.getValue();
if (e.getName().equals(dataProviderID))
{
fireModificationEvent(s);// make sure the change is seen and pushed to display by jtable
}
else
{
obj = dal.getValueObject(s, dataProviderID);
if (obj == Scriptable.NOT_FOUND)
{
obj = null;
}
}
if (s == currentEditingState && table.getEditorComponent() == editor && editor instanceof IDisplayData)
{
convertAndSetValue(((IDisplayData)editor), obj);
}
}
}
finally
{
adjusting = false;
}
}
@Override
public String toString()
{
return "CellAdapter " + dataProviderID + ", hash " + hashCode(); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Returns the editor.
*
* @return Component
*/
public Component getEditor()
{
return editor;
}
/**
* Returns the renderer.
*
* @return Component
*/
public Component getRenderer()
{
return renderer;
}
public String getName()
{
return name;
}
private void onEditorChanged()
{
try
{
table.setLayoutChangingViaJavascript(true);
ArrayList<CellAdapter> cellAdaptersList = new ArrayList<CellAdapter>();
Enumeration<TableColumn> columnsEnum = table.getColumnModel().getColumns();
TableColumn column;
while (columnsEnum.hasMoreElements())
{
column = columnsEnum.nextElement();
cellAdaptersList.add((CellAdapter)column);
}
for (CellAdapter ca : cellAdaptersList)
table.getColumnModel().removeColumn(ca);
Collections.sort(cellAdaptersList, new Comparator<CellAdapter>()
{
public int compare(CellAdapter o1, CellAdapter o2)
{
Component editor1 = o1.getEditor();
Component editor2 = o2.getEditor();
Point p1 = ((editor1 instanceof ISupportCachedLocationAndSize) ? ((ISupportCachedLocationAndSize)editor1).getCachedLocation()
: editor1.getLocation());
Point p2 = ((editor2 instanceof ISupportCachedLocationAndSize) ? ((ISupportCachedLocationAndSize)editor2).getCachedLocation()
: editor2.getLocation());
return PositionComparator.comparePoint(true, p1, p2);
}
});
for (CellAdapter cellAdapter : cellAdaptersList)
{
cellAdapter.updateEditorX();
cellAdapter.updateEditorWidth();
cellAdapter.resetEditorSize();
table.getColumnModel().addColumn(cellAdapter);
}
}
finally
{
table.setLayoutChangingViaJavascript(false);
}
}
public void updateEditorX()
{
this.editorX = (editor instanceof ISupportCachedLocationAndSize) ? ((ISupportCachedLocationAndSize)editor).getCachedLocation().x
: editor.getLocation().x;
}
public void updateEditorWidth()
{
this.editorWidth = (editor instanceof ISupportCachedLocationAndSize) ? ((ISupportCachedLocationAndSize)editor).getCachedSize().width
: editor.getSize().width;
}
private void resetEditorSize()
{
Dimension size;
if (editor instanceof ISupportCachedLocationAndSize)
{
size = ((ISupportCachedLocationAndSize)editor).getCachedSize();
}
else
{
size = editor.getSize();
}
CellAdapter.this.setPreferredWidth(size != null ? size.width + table.getColumnModel().getColumnMargin() : 0);
}
public void itemStateChanged(ItemEvent e)
{
if (e.getSource() instanceof DataComboBox && e.getStateChange() == ItemEvent.DESELECTED)
{
return;
}
stopCellEditing();
}
public void actionPerformed(ActionEvent e)
{
if (currentEditingState != null) currentEditingState.startEditing();
}
public void addDisplay(CellAdapter ca)
{
if (displays == null) displays = new ArrayList<CellAdapter>();
displays.add(ca);
}
public void commitEdit(IDisplayData e)
{
getCellEditorValue();
}
public void startEdit(IDisplayData e)
{
// ignore
}
class ReducedBorder implements Border
{
public static final int LEFT = 2;
public static final int RIGHT = 4;
public static final int TOP = 8;
public static final int BOTTOM = 16;
private final Border sourceBorder;
private final int hideMask;
ReducedBorder(Border sourceBorder, int hideMask)
{
this.sourceBorder = sourceBorder;
this.hideMask = hideMask;
}
/*
* @see javax.swing.border.Border#paintBorder(java.awt.Component, java.awt.Graphics, int, int, int, int)
*/
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height)
{
Insets sourceBorderInsets = sourceBorder.getBorderInsets(c);
Area borderClip = new Area(new Rectangle(x, y, width, height));
if ((hideMask & LEFT) != 0)
{
borderClip.subtract(new Area(new Rectangle(x, y + sourceBorderInsets.top, sourceBorderInsets.left, height - sourceBorderInsets.top -
sourceBorderInsets.bottom)));
}
if ((hideMask & RIGHT) != 0)
{
borderClip.subtract(new Area(new Rectangle(x + width - sourceBorderInsets.right, y + sourceBorderInsets.top, sourceBorderInsets.right, height -
sourceBorderInsets.top - sourceBorderInsets.bottom)));
}
if ((hideMask & TOP) != 0)
{
borderClip.subtract(new Area(new Rectangle(x + sourceBorderInsets.left, y, width - sourceBorderInsets.left - sourceBorderInsets.right,
sourceBorderInsets.top)));
}
if ((hideMask & BOTTOM) != 0)
{
borderClip.subtract(new Area(new Rectangle(x + sourceBorderInsets.left, y + height - sourceBorderInsets.bottom, width -
sourceBorderInsets.left - sourceBorderInsets.right, sourceBorderInsets.bottom)));
}
g.setClip(borderClip);
sourceBorder.paintBorder(c, g, x, y, width, height);
}
/*
* @see javax.swing.border.Border#getBorderInsets(java.awt.Component)
*/
public Insets getBorderInsets(Component c)
{
Insets sourceBorderInsets = sourceBorder.getBorderInsets(c);
int left = (hideMask & LEFT) != 0 ? 0 : sourceBorderInsets.left;
int right = (hideMask & RIGHT) != 0 ? 0 : sourceBorderInsets.right;
int top = (hideMask & TOP) != 0 ? 0 : sourceBorderInsets.top;
int bottom = (hideMask & BOTTOM) != 0 ? 0 : sourceBorderInsets.bottom;
return new Insets(top, left, bottom, right);
}
/*
* @see javax.swing.border.Border#isBorderOpaque()
*/
public boolean isBorderOpaque()
{
return sourceBorder.isBorderOpaque();
}
}
}