/* 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.SwingConstants; import javax.swing.border.Border; import javax.swing.event.ListDataListener; import javax.swing.text.Document; import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; import org.apache.wicket.RequestCycle; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.IAjaxCallDecorator; import org.apache.wicket.ajax.calldecorator.AjaxCallDecorator; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.MarkupStream; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.FormComponent; import org.apache.wicket.model.IModel; 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.IServiceProvider; 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.scripting.JSEvent; import com.servoy.j2db.server.headlessclient.MainPage; import com.servoy.j2db.ui.IEventExecutor; import com.servoy.j2db.ui.IFieldComponent; 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.ISupportValueList; import com.servoy.j2db.ui.ISupportWebBounds; import com.servoy.j2db.ui.scripting.AbstractRuntimeField; import com.servoy.j2db.util.HtmlUtils; import com.servoy.j2db.util.ISupplyFocusChildren; import com.servoy.j2db.util.Text; import com.servoy.j2db.util.Utils; /** * @author lvostinar * */ @SuppressWarnings("nls") public abstract class WebBaseSelectBox extends MarkupContainer implements IFieldComponent, IDisplayData, IProviderStylePropertyChanges, INullableAware, ISupportWebBounds, IRightClickListener, ISupplyFocusChildren<Component>, ISupportValueList, ISupportSimulateBoundsProvider, ISupportOnRender { protected static final long serialVersionUID = 1L; protected static final String NO_COLOR = "NO_COLOR"; protected IValueList onValue; protected final WebEventExecutor eventExecutor; protected Cursor cursor; protected boolean needEntireState; protected int maxLength; protected Insets margin; protected int horizontalAlignment; protected Point loc; protected String inputId; protected boolean allowNull = true; protected String tmpForeground = NO_COLOR; protected final IApplication application; protected final FormComponent< ? > selector; protected final AbstractRuntimeField<IFieldComponent> scriptable; public WebBaseSelectBox(final IApplication application, AbstractRuntimeField<IFieldComponent> scriptable, String id, String text) { super(id); this.scriptable = scriptable; this.application = application; selector = getSelector("check_" + id); //$NON-NLS-1$ boolean useAJAX = Utils.getAsBoolean(application.getRuntimeProperties().get("useAJAX")); eventExecutor = new WebEventExecutor(selector, useAJAX); setOutputMarkupPlaceholderTag(true); add(selector); Label selectLabel = new Label("text_" + id, "") { @Override protected void onComponentTag(ComponentTag tag) { super.onComponentTag(tag); if (tag.getName().compareToIgnoreCase("label") == 0) //$NON-NLS-1$ { tag.put("for", selector.getMarkupId()); //$NON-NLS-1$ } } @Override protected void onBeforeRender() { super.onBeforeRender(); String txt = getDefaultModelObjectAsString(); if (HtmlUtils.startsWithHtml(txt)) { txt = StripHTMLTagsConverter.convertBodyText(this, txt, application.getFlattenedSolution()).getBodyTxt().toString(); setDefaultModelObject(txt); } } }; selectLabel.setOutputMarkupId(true); add(selectLabel); //$NON-NLS-1$ //$NON-NLS-2$ setText(Text.processTags(text, null)); selector.add(new FocusIfInvalidAttributeModifier(selector)); add(StyleAttributeModifierModel.INSTANCE); add(TooltipAttributeModifier.INSTANCE); } protected abstract FormComponent< ? > getSelector(String id); private String text; public void setText(String txt) { this.text = txt; Component c = get("text_" + getId()); //$NON-NLS-1$ if (txt == null || txt.trim().length() == 0) { c.setVisible(false); } else { c.setDefaultModelObject(txt); c.setVisible(true); c.setEscapeModelStrings(false); } } public String getText() { return text; } /** * @see org.apache.wicket.Component#getLocale() */ @Override public Locale getLocale() { return application.getLocale(); } public IStylePropertyChanges getStylePropertyChanges() { return scriptable.getChangesRecorder(); } /* * _____________________________________________________________ Methods for event handling */ public void addScriptExecuter(IScriptExecuter el) { eventExecutor.setScriptExecuter(el); } public IEventExecutor getEventExecutor() { return eventExecutor; } public void setEnterCmds(String[] ids, Object[][] args) { //we cannot add focus in web on checkbox, onAction runs at same time as onfocus, making onfocus useless, and giving unprdicted behaviour //eventExecutor.setEnterCmds(ids); } public void setLeaveCmds(String[] ids, Object[][] args) { eventExecutor.setLeaveCmds(ids, args); } 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; if (!isValueValid) { previousValidValue = oldVal; requestFocusToComponent(); if (tmpForeground == NO_COLOR) { tmpForeground = scriptable.getFgcolor(); scriptable.setFgcolor("red"); } } else { previousValidValue = null; if (tmpForeground != NO_COLOR) { scriptable.setFgcolor(tmpForeground); tmpForeground = NO_COLOR; } } } public Component[] getFocusChildren() { Component component = null; if (onValue != null) component = this; else component = selector; return new Component[] { component }; } public void notifyLastNewValueWasChange(final Object oldVal, final Object newVal) { if (eventExecutor.hasChangeCmd()) { application.invokeLater(new Runnable() { public void run() { WebEventExecutor.setSelectedIndex(WebBaseSelectBox.this, null, IEventExecutor.MODIFIERS_UNSPECIFIED); eventExecutor.fireChangeCommand(previousValidValue == null ? oldVal : previousValidValue, newVal, false, WebBaseSelectBox.this); } }); } else { setValueValid(true, null); } } public void setChangeCmd(String id, Object[] args) { eventExecutor.setChangeCmd(id, args); } public void setActionCmd(String id, Object[] args) { eventExecutor.setActionCmd(id, args); } private boolean wasEditable; public void setValidationEnabled(boolean b) { if (eventExecutor.getValidationEnabled() == b) return; boolean prevEditState = editState; if (b) { setEditable(wasEditable); } else { wasEditable = editable; if (!Boolean.TRUE.equals(application.getClientProperty(IApplication.LEAVE_FIELDS_READONLY_IN_FIND_MODE))) { setEditable(true); } } editState = prevEditState; eventExecutor.setValidationEnabled(b); getStylePropertyChanges().setChanged(); } public void setSelectOnEnter(boolean b) { eventExecutor.setSelectOnEnter(b); } //_____________________________________________________________ /** * @see wicket.markup.html.WebMarkupContainer#onRender() */ @Override protected void onRender(final MarkupStream markupStream) { super.onRender(markupStream); getStylePropertyChanges().setRendered(); IModel< ? > model = getInnermostModel(); if (model instanceof RecordItemModel) { ((RecordItemModel)model).updateRenderedValue(this); } } @Override protected void onComponentTag(ComponentTag tag) { super.onComponentTag(tag); boolean useAJAX = Utils.getAsBoolean(application.getRuntimeProperties().get("useAJAX")); //$NON-NLS-1$ if (useAJAX) { Object oe = scriptable.getClientProperty("ajax.enabled"); if (oe != null) useAJAX = Utils.getAsBoolean(oe); } if (!useAJAX) { Form< ? > f = getForm(); if (f != null) { if (eventExecutor.hasRightClickCmd()) { CharSequence urlr = urlFor(IRightClickListener.INTERFACE); // We need a "return false;" so that the context menu is not displayed in the browser. tag.put("oncontextmenu", f.getJsForInterfaceUrl(urlr) + " return false;"); //$NON-NLS-1$ //$NON-NLS-2$ } } } } /** * @see com.servoy.j2db.ui.IFieldComponent#setNeedEntireState(boolean) */ public void setNeedEntireState(boolean needEntireState) { this.needEntireState = needEntireState; } /** * @see com.servoy.j2db.dataprocessing.IDisplayData#needEntireState() */ public boolean needEntireState() { return needEntireState; } /** * @see com.servoy.j2db.ui.IFieldComponent#setMaxLength(int) */ public void setMaxLength(int maxLength) { this.maxLength = maxLength; } protected boolean editable; protected boolean editState; public void setEditable(boolean b) { editState = b; editable = b; } public boolean isEditable() { return !isReadOnly(); } /** * @see com.servoy.j2db.ui.IFieldComponent#setMargin(java.awt.Insets) */ public void setMargin(Insets margin) { this.margin = margin; } public Insets getMargin() { return margin; } /** * @see com.servoy.j2db.ui.IFieldComponent#setHorizontalAlignment(int) */ public void setHorizontalAlignment(int horizontalAlignment) { this.horizontalAlignment = horizontalAlignment; } /** * @see com.servoy.j2db.ui.IComponent#setCursor(java.awt.Cursor) */ public void setCursor(Cursor cursor) { this.cursor = cursor; } public Object getValueObject() { return getDefaultModelObject(); } public void setAllowNull(boolean allowNull) { this.allowNull = allowNull; } public boolean getAllowNull() { return allowNull; } public boolean needEditListener() { return false; } public void addEditListener(IEditListener l) { //ignore } /** * @see com.servoy.j2db.dataprocessing.IDisplayData#getDocument() */ public Document getDocument() { return null; } public boolean stopUIEditing(boolean looseFocus) { if (!isValueValid) { requestFocusToComponent(); return false; } return true; } public void requestFocusToComponent() { // is the current container always the right one... IMainContainer currentContainer = ((FormManager)application.getFormManager()).getCurrentContainer(); if (currentContainer instanceof MainPage) { ((MainPage)currentContainer).componentToFocus(selector); } } public void setName(String n) { name = n; } private String name; public String getName() { return name; } /* * border--------------------------------------------------- */ protected Border border; public void setBorder(Border border) { this.border = border; } public Border getBorder() { return border; } /* * opaque--------------------------------------------------- */ public void setOpaque(boolean opaque) { this.opaque = opaque; } protected boolean opaque; // Searches for a parent form, up the hierarchy of controls in the page. protected Form< ? > getForm() { Component c = this; while ((c != null) && !(c instanceof Form)) c = c.getParent(); return (Form< ? >)c; } @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 = this.equals(((MainPage)currentContainer).getFocusedComponent()); } if (force) scriptable.getRenderEventExecutor().setRenderStateChanged(); scriptable.getRenderEventExecutor().fireOnRender(isFocused); } } public IValueList getValueList() { return onValue; } public ListDataListener getListener() { return null; } public void setValueList(IValueList vl) { this.onValue = vl; getStylePropertyChanges().setChanged(); } /* * readonly--------------------------------------------------- */ public void setReadOnly(boolean b) { if (b && !editable) return; if (b) { setEditable(false); editState = true; } else { setEditable(editState); } } public boolean isReadOnly() { return !editable; } /* * dataprovider--------------------------------------------------- */ protected String dataProviderID; public void setDataProviderID(String dataProviderID) { this.dataProviderID = dataProviderID; } public String getDataProviderID() { return dataProviderID; } public boolean isOpaque() { return opaque; } 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); } private String tooltip; public void setToolTipText(String tooltip) { this.tooltip = Utils.stringIsEmpty(tooltip) ? null : tooltip; } protected ITagResolver resolver; public void setTagResolver(ITagResolver resolver) { this.resolver = resolver; } /** * @see com.servoy.j2db.ui.IComponent#getToolTipText() */ public String getToolTipText() { if (tooltip != null && getInnermostModel() instanceof RecordItemModel) { return Text.processTags(tooltip, resolver); } return tooltip; } /* * font--------------------------------------------------- */ public void setFont(Font font) { this.font = font; } private Font font; public Font getFont() { return font; } private Color background; public void setBackground(Color cbg) { this.background = cbg; } public Color getBackground() { return background; } private Color foreground; private List<ILabel> labels; public void setForeground(Color cfg) { this.foreground = 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); 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() { if (size != null) { Dimension d = ((ChangesRecorder)scriptable.getChangesRecorder()).calculateWebSize(size.width, size.height, border, margin, 0, null, true, SwingConstants.CENTER); return new Rectangle(location, d); } return null; } /** * @see com.servoy.j2db.ui.ISupportWebBounds#getPaddingAndBorder() */ public Insets getPaddingAndBorder() { return ((ChangesRecorder)scriptable.getChangesRecorder()).getPaddingAndBorder(size == null ? 0 : size.height, border, margin, 0, null, true, SwingConstants.CENTER); } public void setSize(Dimension size) { this.size = size; } public void setRightClickCommand(String rightClickCmd, Object[] args) { eventExecutor.setRightClickCmd(rightClickCmd, args); add(new ServoyAjaxEventBehavior("oncontextmenu", "SelectBox", true) //$NON-NLS-1$ { @Override protected void onEvent(AjaxRequestTarget target) { eventExecutor.onEvent(JSEvent.EventType.rightClick, target, WebBaseSelectBox.this, Utils.getAsInteger(RequestCycle.get().getRequest().getParameter(IEventExecutor.MODIFIERS_PARAMETER))); } @Override public boolean isEnabled(Component component) { if (super.isEnabled(component)) { Object oe = WebBaseSelectBox.this.scriptable.getClientProperty("ajax.enabled"); if (oe != null) return Utils.getAsBoolean(oe); return true; } return false; } // We need to return false, otherwise the context menu of the browser is displayed. @Override protected IAjaxCallDecorator getAjaxCallDecorator() { return new AjaxCallDecorator() { @Override public CharSequence decorateScript(CharSequence script) { return script + " return false;"; } }; } }); } public void onRightClick() { Form< ? > f = getForm(); if (f != null) { // If form validation fails, we don't execute the method. if (f.process()) eventExecutor.onEvent(JSEvent.EventType.rightClick, null, this, IEventExecutor.MODIFIERS_UNSPECIFIED); } } public ISupportSimulateBounds getBoundsProvider() { return findParent(ISupportSimulateBounds.class); } interface ISelector { public WebBaseSelectBox getSelectBox(); } }