/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.server.headlessclient.dataui; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.util.ArrayList; import java.util.List; import java.util.Locale; import javax.swing.border.Border; import javax.swing.text.Document; import org.apache.wicket.Component; import org.apache.wicket.markup.MarkupStream; import org.apache.wicket.markup.html.WebMarkupContainer; import com.servoy.base.util.ITagResolver; import com.servoy.j2db.FormManager; import com.servoy.j2db.IApplication; import com.servoy.j2db.IMainContainer; import com.servoy.j2db.IScriptExecuter; import com.servoy.j2db.component.ComponentFormat; import com.servoy.j2db.dataprocessing.IDisplayData; import com.servoy.j2db.dataprocessing.IEditListener; import com.servoy.j2db.server.headlessclient.IDesignModeListener; import com.servoy.j2db.server.headlessclient.MainPage; import com.servoy.j2db.ui.IComponent; import com.servoy.j2db.ui.IEventExecutor; import com.servoy.j2db.ui.IFieldComponent; import com.servoy.j2db.ui.IFormattingComponent; import com.servoy.j2db.ui.ILabel; import com.servoy.j2db.ui.IProviderStylePropertyChanges; import com.servoy.j2db.ui.IStylePropertyChanges; import com.servoy.j2db.ui.ISupportOnRender; import com.servoy.j2db.ui.ISupportSimulateBounds; import com.servoy.j2db.ui.ISupportSimulateBoundsProvider; import com.servoy.j2db.ui.ISupportWebBounds; import com.servoy.j2db.ui.scripting.AbstractRuntimeField; import com.servoy.j2db.ui.scripting.IFormatScriptComponent; import com.servoy.j2db.ui.scripting.RuntimeDataField; import com.servoy.j2db.util.IDelegate; import com.servoy.j2db.util.ISupplyFocusChildren; import com.servoy.j2db.util.PersistHelper; /** * Represents a component based on a text field to which it adds more (sub)components for added functionality. * For example calendar, spinner. * * @author jcompagner */ public abstract class WebDataCompositeTextField extends WebMarkupContainer implements IFieldComponent, IDisplayData, IDelegate, ISupportWebBounds, IRightClickListener, IProviderStylePropertyChanges, ISupplyFocusChildren<Component>, IFormattingComponent, IDesignModeListener, ISupportSimulateBoundsProvider, ISupportOnRender { private static final long serialVersionUID = 1L; public static final String AUGMENTED_FIELD_ID = "_AF"; //$NON-NLS-1$ protected final WebDataField field; protected final IApplication application; protected boolean readOnly = false; protected boolean showExtraComponents = true; protected boolean editable; private Insets margin; private final AbstractRuntimeField<IFieldComponent> scriptable; private boolean designMode = false; public WebDataCompositeTextField(IApplication application, AbstractRuntimeField<IFieldComponent> scriptable, String id) { super(id); this.application = application; this.scriptable = scriptable; RuntimeDataField fieldScriptable = new RuntimeDataField(new ChangesRecorder(TemplateGenerator.DEFAULT_FIELD_BORDER_SIZE, TemplateGenerator.DEFAULT_FIELD_PADDING), application); field = createTextField(fieldScriptable); fieldScriptable.setComponent(field, null); field.setIgnoreOnRender(true); add(field); // because the composite field will probably add one or more html tags tag besides the field component // each time that component is rendered, we must make sure that we render the whole container; // otherwise, each independent render of the field component will add one more unwanted tags to the composite field markup ((ChangesRecorder)scriptable.getChangesRecorder()).setAdditionalChangesRecorder(field.getStylePropertyChanges()); setOutputMarkupPlaceholderTag(true); add(StyleAttributeModifierModel.INSTANCE); add(TooltipAttributeModifier.INSTANCE); } protected WebDataField createTextField(RuntimeDataField fieldScriptable) { return new AugmentedTextField(application, fieldScriptable, this); } protected String getTextFieldId() { return getId() + AUGMENTED_FIELD_ID; } public AbstractRuntimeField<IFieldComponent> getScriptObject() { return scriptable; } protected boolean shouldShowExtraComponents() { return isEnabledInHierarchy() && showExtraComponents && !designMode; } public Component[] getFocusChildren() { return new Component[] { field }; } @Override public Locale getLocale() { return application.getLocale(); } @Override protected void onRender(MarkupStream markupStream) { super.onRender(markupStream); getStylePropertyChanges().setRendered(); } public IStylePropertyChanges getStylePropertyChanges() { return scriptable.getChangesRecorder(); } public Object getDelegate() { return field; } public Document getDocument() { return field.getDocument(); } public void setMargin(Insets i) { this.margin = i; } public Insets getMargin() { return margin; } public void addScriptExecuter(IScriptExecuter el) { field.addScriptExecuter(el); } public IEventExecutor getEventExecutor() { return field.getEventExecutor(); } public void setEnterCmds(String[] ids, Object[][] args) { field.setEnterCmds(ids, null); } public void setLeaveCmds(String[] ids, Object[][] args) { field.setLeaveCmds(ids, null); } public void setActionCmd(String id, Object[] args) { field.setActionCmd(id, args); } public void notifyLastNewValueWasChange(Object oldVal, Object newVal) { field.notifyLastNewValueWasChange(oldVal, newVal); } public boolean isValueValid() { return field.isValueValid(); } public void setValueValid(boolean valid, Object oldVal) { field.setValueValid(valid, oldVal); } public void setChangeCmd(String id, Object[] args) { field.setChangeCmd(id, args); } public void setHorizontalAlignment(int a) { field.setHorizontalAlignment(a); } public void setMaxLength(int i) { field.setMaxLength(i); } public void addEditListener(IEditListener l) { if (field != null) field.addEditListener(l); } public void setValueObject(Object obj) { field.setValueObject(obj); } public Object getValueObject() { return field.getValue(); } public boolean needEditListener() { return true; } public boolean needEntireState() { return field.needEntireState(); } public void setNeedEntireState(boolean b) { field.setNeedEntireState(b); } protected ITagResolver resolver; public void setTagResolver(ITagResolver resolver) { this.resolver = resolver; } public void setValidationEnabled(boolean b) { field.setValidationEnabled(b); if (b) { if (showExtraComponents == readOnly) { showExtraComponents = !readOnly; getStylePropertyChanges().setChanged(); } } else { if (!Boolean.TRUE.equals(application.getClientProperty(IApplication.LEAVE_FIELDS_READONLY_IN_FIND_MODE))) { boolean oldReadonly = readOnly; setReadOnly(false); readOnly = oldReadonly; } } } public boolean stopUIEditing(boolean looseFocus) { if (field != null) return field.stopUIEditing(looseFocus); return true; } public void setSelectOnEnter(boolean b) { if (field != null) field.setSelectOnEnter(b); } public void setCursor(Cursor cursor) { // nothing here yet } @Override public String toString() { return scriptable != null ? scriptable.toString() : super.toString(); } public void requestFocusToComponent() { field.requestFocusToComponent(); } public String getDataProviderID() { return field.getDataProviderID(); } public void setDataProviderID(String id) { field.setDataProviderID(id); } /* * format--------------------------------------------------- */ public void installFormat(ComponentFormat componentFormat) { ((IFormatScriptComponent)field.getScriptObject()).setComponentFormat(componentFormat); } public boolean isEditable() { return editable; } public void setEditable(boolean b) { field.setEditable(b); editable = b; } public void setReadOnly(boolean b) { if (readOnly != b) { readOnly = b; showExtraComponents = !b; field.setReadOnly(b); } } public boolean isReadOnly() { return !showExtraComponents; } public void setName(String n) { name = n; field.setName(n); } private String name; public String getName() { return name; } /* * border--------------------------------------------------- */ private Border border; public void setBorder(Border border) { this.border = border; } public Border getBorder() { return border; } /* * opaque--------------------------------------------------- */ public void setOpaque(boolean opaque) { this.opaque = opaque; } private boolean opaque; public boolean isOpaque() { return opaque; } /* * titleText--------------------------------------------------- */ public void setTitleText(String title) { field.setTitleText(title); } public String getTitleText() { return field.getTitleText(); } /* * tooltip--------------------------------------------------- */ public void setToolTipText(String tip) { field.setToolTipText(tip); } public String getToolTipText() { return field.getToolTipText(); } /* * font--------------------------------------------------- */ private Font font; public Font getFont() { return font; } public void setFont(Font f) { font = f; if (f != null && field != null) { field.setFont(f); } } private Color background; public void setBackground(Color cbg) { this.background = cbg; } public Color getBackground() { return background; } private Color foreground; private ArrayList<ILabel> labels; public void setForeground(Color cfg) { this.foreground = cfg; if (field != null) { field.getScriptObject().setFgcolor(PersistHelper.createColorString(cfg)); } } public Color getForeground() { return foreground; } /* * visible--------------------------------------------------- */ public void setComponentVisible(boolean visible) { if (viewable || !visible) { setVisible(visible); if (labels != null) { for (int i = 0; i < labels.size(); i++) { ILabel label = labels.get(i); label.setComponentVisible(visible); } } } } public void addLabelFor(ILabel label) { if (labels == null) labels = new ArrayList<ILabel>(3); labels.add(label); } public List<ILabel> getLabelsFor() { return labels; } public void setComponentEnabled(final boolean b) { if (accessible || !b) { super.setEnabled(b); field.setEnabled(b); getStylePropertyChanges().setChanged(); if (labels != null) { for (int i = 0; i < labels.size(); i++) { ILabel label = labels.get(i); label.setComponentEnabled(b); } } } } 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; } /* * location--------------------------------------------------- */ private Point location = new Point(0, 0); public int getAbsoluteFormLocationY() { WebDataRenderer parent = findParent(WebDataRenderer.class); if (parent != null) { return parent.getYOffset() + getLocation().y; } return getLocation().y; } public void setLocation(Point location) { this.location = location; } public Point getLocation() { return location; } /* * size--------------------------------------------------- */ private Dimension size = new Dimension(0, 0); public Dimension getSize() { return size; } public Rectangle getWebBounds() { Dimension d = ((ChangesRecorder)getStylePropertyChanges()).calculateWebSize(size.width, size.height, border, null, 0, null); return new Rectangle(location, d); } public Insets getPaddingAndBorder() { return ((ChangesRecorder)getStylePropertyChanges()).getPaddingAndBorder(size.height, border, null, 0, null); } public void setSize(Dimension size) { this.size = size; } public void setRightClickCommand(String rightClickCmd, Object[] args) { field.setRightClickCommand(rightClickCmd, args); } public void onRightClick() { field.onRightClick(); } public void setDesignMode(boolean mode) { designMode = mode; } @Override protected void onBeforeRender() { super.onBeforeRender(); fireOnRender(false); } public void fireOnRender(boolean force) { if (scriptable != null) { boolean isFocused = false; IMainContainer currentContainer = ((FormManager)application.getFormManager()).getCurrentContainer(); if (currentContainer instanceof MainPage) { isFocused = field.equals(((MainPage)currentContainer).getFocusedComponent()); } if (force) scriptable.getRenderEventExecutor().setRenderStateChanged(); scriptable.getRenderEventExecutor().fireOnRender(isFocused); } } public ISupportSimulateBounds getBoundsProvider() { return findParent(ISupportSimulateBounds.class); } protected class AugmentedTextField extends WebDataField { public AugmentedTextField(IApplication application, RuntimeDataField scriptable, IComponent enclosingComponent) { super(application, scriptable, getTextFieldId(), enclosingComponent); } // When the composite field is neither editable nor read-only, we want the text field to be not editable, but // we want to let the user change the (date) value using the extra components. // In this case we need the read only and filter backspace behaviors of the text field to work normally (they are enabled based // on the "editable" member - which is set from the composite field), but we also need the normal onChange behavior to be enabled - so as the data is updated on the server even if the text field itself is read-only. // Because the onChange uses accessor methods, if we overwrite those to always return editable = true and read-only = false, we should // get the expected behavior. @Override public boolean isReadOnly() { return false; } @Override public boolean isEditable() { return true; } @Override public String getMarkupId() { return WebDataCompositeTextField.this.getMarkupId() + "compositefield"; } } }