/*
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.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JRadioButton;
import javax.swing.event.ListDataListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.text.Document;
import sun.java2d.SunGraphics2D;
import com.servoy.base.util.ITagResolver;
import com.servoy.j2db.IApplication;
import com.servoy.j2db.IScriptExecuter;
import com.servoy.j2db.IServiceProvider;
import com.servoy.j2db.component.IMarginAwareBorder;
import com.servoy.j2db.component.INullableAware;
import com.servoy.j2db.dataprocessing.IDisplayData;
import com.servoy.j2db.dataprocessing.IEditListener;
import com.servoy.j2db.dataprocessing.IValueList;
import com.servoy.j2db.ui.IDataRenderer;
import com.servoy.j2db.ui.IEventExecutor;
import com.servoy.j2db.ui.IFieldComponent;
import com.servoy.j2db.ui.ILabel;
import com.servoy.j2db.ui.ISupportCachedLocationAndSize;
import com.servoy.j2db.ui.ISupportOnRender;
import com.servoy.j2db.ui.ISupportValueList;
import com.servoy.j2db.ui.scripting.AbstractRuntimeValuelistComponent;
import com.servoy.j2db.util.HtmlUtils;
import com.servoy.j2db.util.ISkinnable;
import com.servoy.j2db.util.Text;
import com.servoy.j2db.util.Utils;
/**
* Runtime swing radio box component
*
* @author lvostinar
*
*/
public class DataRadioButton extends JRadioButton implements IFieldComponent, IDisplayData, ISkinnable, INullableAware, ISupportCachedLocationAndSize,
ISupportValueList, IMarginAwareBorder, ISupportOnRender
{
private Object value;
protected IValueList onValue;
protected IApplication application;
private String tooltip;
private final EventExecutor eventExecutor;
private MouseAdapter rightclickMouseAdapter = null;
private boolean allowNull = true;
private final AbstractRuntimeValuelistComponent<IFieldComponent> scriptable;
public DataRadioButton(IApplication application, AbstractRuntimeValuelistComponent<IFieldComponent> scriptable, String text)
{
setText(Text.processTags(text, null));
this.application = application;
eventExecutor = new EventExecutor(this);
addKeyListener(eventExecutor);
this.scriptable = scriptable;
}
public DataRadioButton(IApplication application, AbstractRuntimeValuelistComponent<IFieldComponent> scriptable, String text, IValueList onValue)
{
this(application, scriptable, text);
this.onValue = onValue;
}
public final AbstractRuntimeValuelistComponent<IFieldComponent> getScriptObject()
{
return scriptable;
}
/**
* Fix for bad font rendering (bad kerning == strange spacing) in java 1.5 see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5097047
*/
@Override
public FontMetrics getFontMetrics(Font font)
{
if (application != null)//getFontMetrics can be called in the constructor super call before application is assigned
{
boolean isPrinting = Utils.getAsBoolean(application.getRuntimeProperties().get("isPrinting")); //$NON-NLS-1$
if (isPrinting)
{
Graphics g = (Graphics)application.getRuntimeProperties().get("printGraphics"); //$NON-NLS-1$
if (g != null)
{
String text = getText();
// only return print graphics font metrics if text does not start with 'W',
// because of left side bearing issue
if (!(text != null && text.length() > 0 && text.charAt(0) == 'W')) return g.getFontMetrics(font);
}
}
}
return super.getFontMetrics(font);
}
public Document getDocument()
{
return null;
}
@Override
public void setUI(ComponentUI ui)
{
super.setUI(ui);
}
/*
* _____________________________________________________________ Methods for event handling
*/
public void addScriptExecuter(IScriptExecuter el)
{
eventExecutor.setScriptExecuter(el);
}
public IEventExecutor getEventExecutor()
{
return eventExecutor;
}
public void setEnterCmds(String[] ids, Object[][] args)
{
eventExecutor.setEnterCmds(ids, args);
}
public void setLeaveCmds(String[] ids, Object[][] args)
{
eventExecutor.setLeaveCmds(ids, args);
}
private boolean wasEditable;
public boolean isValueValid()
{
return isValueValid;
}
private boolean isValueValid = true;
private Object previousValidValue;
public void setValueValid(boolean valid, Object oldVal)
{
application.getRuntimeProperties().put(IServiceProvider.RT_LASTFIELDVALIDATIONFAILED_FLAG, Boolean.valueOf(!valid));
isValueValid = valid;
repaint(); // foreground color changes
if (!isValueValid)
{
previousValidValue = oldVal;
application.invokeLater(new Runnable()
{
public void run()
{
requestFocus();
}
});
}
else
{
previousValidValue = null;
}
}
@Override
public Color getForeground()
{
if (isValueValid())
{
return super.getForeground();
}
return Color.red;
}
public void notifyLastNewValueWasChange(Object oldVal, Object newVal)
{
if (previousValidValue != null)
{
oldVal = previousValidValue;
}
eventExecutor.fireChangeCommand(oldVal, newVal, false, this);
//if change cmd is not succeeded also don't call action cmd?
if (isValueValid)
{
eventExecutor.fireActionCommand(false, this);
}
}
public void setChangeCmd(String id, Object[] args)
{
eventExecutor.setChangeCmd(id, args);
}
public void setActionCmd(String id, Object[] args)
{
eventExecutor.setActionCmd(id, args);
}
public void setRightClickCommand(String rightClickCmd, Object[] args)
{
eventExecutor.setRightClickCmd(rightClickCmd, args);
if (rightClickCmd != null && rightclickMouseAdapter == null)
{
rightclickMouseAdapter = new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent e)
{
if (e.isPopupTrigger()) handle(e);
}
@Override
public void mouseReleased(MouseEvent e)
{
if (e.isPopupTrigger()) handle(e);
}
private void handle(MouseEvent e)
{
if (scriptable.isEnabled())
{
eventExecutor.fireRightclickCommand(true, DataRadioButton.this, e.getModifiers(), e.getPoint());
}
}
};
addMouseListener(rightclickMouseAdapter);
}
}
public void setValidationEnabled(boolean b)
{
if (eventExecutor.getValidationEnabled() == b) return;
boolean prevEditState = editState;
if (b)
{
setEditable(wasEditable);
}
else
{
wasEditable = !isReadOnly();
if (!Boolean.TRUE.equals(application.getClientProperty(IApplication.LEAVE_FIELDS_READONLY_IN_FIND_MODE)))
{
setEditable(true);
}
}
eventExecutor.setValidationEnabled(b);
editState = prevEditState;
}
public void setSelectOnEnter(boolean b)
{
eventExecutor.setSelectOnEnter(b);
}
//_____________________________________________________________
public void setMaxLength(int i)
{
//ignore
}
private boolean needEntireState;
public boolean needEntireState()
{
return needEntireState;
}
public void setNeedEntireState(boolean b)
{
needEntireState = b;
}
public boolean needEditListener()
{
return true;
}
private EditProvider editProvider = null;
public void addEditListener(IEditListener l)
{
if (editProvider == null)
{
editProvider = new EditProvider(this, application);
addFocusListener(editProvider);
addItemListener(editProvider);
editProvider.addEditListener(l);
}
}
public String getDataProviderID()
{
return dataProviderID;
}
private String dataProviderID;
public void setDataProviderID(String id)
{
dataProviderID = id;
}
@Override
public void setToolTipText(String tip)
{
if (tip != null && tip.indexOf("%%") != -1) //$NON-NLS-1$
{
tooltip = tip;
// register an empty one so that this component is registered as a tooltipper..
super.setToolTipText(""); //$NON-NLS-1$
}
else if (!Utils.stringIsEmpty(tip))
{
if (!Utils.stringContainsIgnoreCase(tip, "<html")) //$NON-NLS-1$
{
super.setToolTipText(tip);
}
else if (HtmlUtils.hasUsefulHtmlContent(tip))
{
super.setToolTipText(tip);
}
}
else
{
super.setToolTipText(null);
}
}
/**
* @see com.servoy.j2db.dataprocessing.IDisplayData#getValueObject()
*/
public Object getValueObject()
{
if (onValue != null && onValue.getSize() >= 1)
{
return (isSelected() ? (onValue.hasRealValues() ? onValue.getRealElementAt(0) : onValue.getElementAt(0)) : value);
}
return value;
}
/**
* @see javax.swing.JComponent#getToolTipText()
*/
@Override
public String getToolTipText()
{
if (resolver != null && tooltip != null)
{
String oldValue = tooltip;
tooltip = null;
super.setToolTipText(Text.processTags(oldValue, resolver));
tooltip = oldValue;
}
return super.getToolTipText();
}
/**
* @see javax.swing.JComponent#getToolTipText(java.awt.event.MouseEvent)
*/
@Override
public String getToolTipText(MouseEvent event)
{
return getToolTipText();
}
protected ITagResolver resolver;
public void setTagResolver(ITagResolver resolver)
{
this.resolver = resolver;
}
/**
* @see com.servoy.j2db.dataprocessing.IDisplayData#setValueObject(Object)
*/
public void setValueObject(Object data)
{
try
{
if (editProvider != null) editProvider.setAdjusting(true);
if (tooltip != null)
{
super.setToolTipText(""); //$NON-NLS-1$
}
if (!Utils.equalObjects(getValueObject(), data))
{
setValueValid(true, null);
}
this.value = data;
if (onValue != null && onValue.getSize() >= 1)
{
Object valuelistValue = null;
if (onValue.hasRealValues())
{
valuelistValue = onValue.getRealElementAt(0);
}
else
{
valuelistValue = onValue.getElementAt(0);
}
setSelected(Utils.equalObjects(value, valuelistValue));
}
}
finally
{
if (editProvider != null) editProvider.setAdjusting(false);
}
fireOnRender(false);
}
public void fireOnRender(boolean force)
{
if (scriptable != null)
{
if (force) scriptable.getRenderEventExecutor().setRenderStateChanged();
scriptable.getRenderEventExecutor().fireOnRender(hasFocus());
}
}
public void setComponentEnabled(final boolean b)
{
if (accessible || !b)
{
enabled = b;
super.setEnabled(enabled && !readonly);
if (labels != null)
{
for (int i = 0; i < labels.size(); i++)
{
ILabel label = labels.get(i);
label.setComponentEnabled(b);
}
}
}
}
public void setEditable(boolean b)
{
if (accessible)
{
editState = b;
readonly = !b;
super.setEnabled(enabled && !readonly);
}
}
public boolean isEditable()
{
return !isReadOnly();
}
private boolean accessible = true;
public void setAccessible(boolean b)
{
if (!b) setComponentEnabled(b);
accessible = b;
}
private boolean viewable = true;
public void setViewable(boolean b)
{
if (!b) setComponentVisible(b);
this.viewable = b;
}
public boolean isViewable()
{
return viewable;
}
private boolean editState;
private ArrayList<ILabel> labels;
private boolean enabled = true;
private boolean readonly = false;
public void setReadOnly(boolean b)
{
if (b && isReadOnly()) return;
if (b)
{
if (accessible)
{
readonly = b;
super.setEnabled(!readonly && enabled);
}
}
else
{
if (accessible)
{
readonly = b;
super.setEnabled(editState && enabled);
}
}
}
public boolean isReadOnly()
{
return readonly;
}
public void setComponentVisible(boolean b_visible)
{
if (viewable || !b_visible)
{
setVisible(b_visible);
}
}
@Override
public void setVisible(boolean flag)
{
super.setVisible(flag);
if (labels != null)
{
for (int i = 0; i < labels.size(); i++)
{
ILabel label = labels.get(i);
label.setComponentVisible(flag);
}
}
}
public void addLabelFor(ILabel label)
{
if (labels == null) labels = new ArrayList(3);
labels.add(label);
}
public List<ILabel> getLabelsFor()
{
return labels;
}
private String titleText = null;
public void setTitleText(String title)
{
this.titleText = title;
setText(Text.processTags(title, resolver));
}
public String getTitleText()
{
return Text.processTags(titleText, resolver);
}
public int getAbsoluteFormLocationY()
{
Container parent = getParent();
while ((parent != null) && !(parent instanceof IDataRenderer))
{
parent = parent.getParent();
}
if (parent != null)
{
return ((IDataRenderer)parent).getYOffset() + getLocation().y;
}
return getLocation().y;
}
private Point cachedLocation;
public Point getCachedLocation()
{
return cachedLocation;
}
public void setCachedLocation(Point location)
{
this.cachedLocation = location;
}
public void setCachedSize(Dimension size)
{
this.cachedSize = size;
}
private Dimension cachedSize;
public Dimension getCachedSize()
{
return cachedSize;
}
public IValueList getValueList()
{
return onValue;
}
public ListDataListener getListener()
{
return null;
}
public void setValueList(IValueList vl)
{
onValue = vl;
}
// If component not shown or not added yet
// and request focus is called it should wait for the component
// to be created.
boolean wantFocus = false;
@Override
public void addNotify()
{
super.addNotify();
if (wantFocus)
{
wantFocus = false;
requestFocus();
}
}
public void requestFocusToComponent()
{
// if (!hasFocus()) Don't test on hasFocus (it can have focus,but other component already did requestFocus)
{
if (isDisplayable())
{
// Must do it in a runnable or else others after a script can get focus first again..
application.invokeLater(new Runnable()
{
public void run()
{
requestFocus();
}
});
}
else
{
wantFocus = true;
}
}
}
@Override
public String toString()
{
return scriptable.toString();
}
public boolean stopUIEditing(boolean looseFocus)
{
if (!isValueValid)
{
application.invokeLater(new Runnable()
{
public void run()
{
requestFocus();
}
});
return false;
}
return true;
}
public String getId()
{
return (String)getClientProperty("Id");
}
/**
* If the check-box is linked to a non-null integer table column, it must force null to become 0 (it is normally shown as unchecked for null) so that the
* user does not need to check/uncheck it for save. This tells the check-box if it is linked to an allowNull field or not. By default allowNull = true.
*
* @param allowNull true if it should allow null values for integer data-providers (for unchecked) and false if it should change null value to value 0.
*/
public void setAllowNull(boolean allowNull)
{
this.allowNull = allowNull;
}
public boolean getAllowNull()
{
return allowNull;
}
@Override
public void paint(Graphics g)
{
// If we have regular SunGraphics2D object, just forward to superclass.
if (g instanceof SunGraphics2D)
{
super.paint(g);
}
else
{
// If we are on Mac OS, we paint first to image buffer and then
// to actual graphics. This is because the Aqua L&F on Mac does
// not paint properly on graphics object that are not instances
// of SunGraphics2D.
if (Utils.isAppleMacOS())
{
// Create buffered image and send it to be painted by superclass.
int width = this.getWidth();
int height = this.getHeight();
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D gr1 = (Graphics2D)img.getGraphics().create();
super.paint(gr1);
gr1.dispose();
// Paint the image to the graphics that we received.
Graphics2D g2d = (Graphics2D)g;
g2d.drawRenderedImage(img, null);
}
// If we are not on Mac OS, just forward to superclass.
else
{
super.paint(g);
}
}
}
}