com.servoy.j2db.server.headlessclient.dataui.WebTabPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.servoy.j2db.server.headlessclient.dataui.WebTabPanel.java

Source

/*
 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 javax.swing.border.Border;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.IRequestTarget;
import org.apache.wicket.IResourceListener;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.Page;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.Session;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.list.Loop;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.protocol.http.ClientProperties;
import org.apache.wicket.protocol.http.request.WebClientInfo;
import org.apache.wicket.version.undo.Change;

import com.servoy.base.util.ITagResolver;
import com.servoy.j2db.FlattenedSolution;
import com.servoy.j2db.FormController;
import com.servoy.j2db.IApplication;
import com.servoy.j2db.IForm;
import com.servoy.j2db.IScriptExecuter;
import com.servoy.j2db.component.ComponentFactory;
import com.servoy.j2db.dataprocessing.IDisplayRelatedData;
import com.servoy.j2db.dataprocessing.IFoundSetInternal;
import com.servoy.j2db.dataprocessing.IRecordInternal;
import com.servoy.j2db.dataprocessing.ISaveConstants;
import com.servoy.j2db.dataprocessing.ISwingFoundSet;
import com.servoy.j2db.dataprocessing.RelatedFoundSet;
import com.servoy.j2db.dataprocessing.SortColumn;
import com.servoy.j2db.dataprocessing.TagResolver;
import com.servoy.j2db.persistence.IAnchorConstants;
import com.servoy.j2db.persistence.Media;
import com.servoy.j2db.persistence.StaticContentSpecLoader;
import com.servoy.j2db.persistence.TabPanel;
import com.servoy.j2db.server.headlessclient.MainPage;
import com.servoy.j2db.server.headlessclient.TabIndexHelper;
import com.servoy.j2db.server.headlessclient.WebClientSession;
import com.servoy.j2db.server.headlessclient.WebForm;
import com.servoy.j2db.ui.IComponent;
import com.servoy.j2db.ui.IFormLookupPanel;
import com.servoy.j2db.ui.IFormUI;
import com.servoy.j2db.ui.IProviderStylePropertyChanges;
import com.servoy.j2db.ui.IStylePropertyChanges;
import com.servoy.j2db.ui.ISupportSecuritySettings;
import com.servoy.j2db.ui.ISupportSimulateBounds;
import com.servoy.j2db.ui.ISupportSimulateBoundsProvider;
import com.servoy.j2db.ui.ISupportWebBounds;
import com.servoy.j2db.ui.ITabPanel;
import com.servoy.j2db.ui.runtime.IRuntimeComponent;
import com.servoy.j2db.ui.scripting.RuntimeTabPanel;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.HtmlUtils;
import com.servoy.j2db.util.PersistHelper;
import com.servoy.j2db.util.Utils;

/**
 * Represents a tabpanel in the webbrowser.
 *
 * @author jcompagner
 */
public class WebTabPanel extends WebMarkupContainer implements ITabPanel, IDisplayRelatedData,
        IProviderStylePropertyChanges, ISupportSecuritySettings, ISupportWebBounds, ISupportWebTabSeq,
        ListSelectionListener, IWebFormContainer, ISupportSimulateBoundsProvider {
    private static final long serialVersionUID = 1L;

    private final IApplication application;
    private WebTabFormLookup currentForm;
    protected IRecordInternal parentData;
    private final List<String> allRelationNames = new ArrayList<String>(5);
    protected final List<WebTabHolder> allTabs = new ArrayList<WebTabHolder>(5);
    private final List<ISwingFoundSet> related = new ArrayList<ISwingFoundSet>();

    private IScriptExecuter scriptExecutor;

    private String onTabChangeMethodCmd;
    private Object[] onTabChangeArgs;

    protected final int orient;
    private int tabSequenceIndex = ISupportWebTabSeq.DEFAULT;
    private Dimension tabSize;
    private final RuntimeTabPanel scriptable;

    public WebTabPanel(IApplication application, final RuntimeTabPanel scriptable, String name, int orient,
            boolean oneTab) {
        super(name);
        this.application = application;
        this.orient = orient;

        final boolean useAJAX = Utils.getAsBoolean(application.getRuntimeProperties().get("useAJAX")); //$NON-NLS-1$
        setOutputMarkupPlaceholderTag(true);

        if (orient != TabPanel.SPLIT_HORIZONTAL && orient != TabPanel.SPLIT_VERTICAL)
            add(new Label("webform", new Model<String>("")));//temporary add, in case the tab panel does not contain any tabs //$NON-NLS-1$ //$NON-NLS-2$

        // TODO check ignore orient and oneTab??
        IModel<Integer> tabsModel = new AbstractReadOnlyModel<Integer>() {
            private static final long serialVersionUID = 1L;

            @Override
            public Integer getObject() {
                return Integer.valueOf(allTabs.size());
            }
        };

        if (orient != TabPanel.HIDE && orient != TabPanel.SPLIT_HORIZONTAL && orient != TabPanel.SPLIT_VERTICAL
                && !(orient == TabPanel.DEFAULT_ORIENTATION && oneTab)) {
            add(new Loop("tablinks", tabsModel) //$NON-NLS-1$
            {
                private static final long serialVersionUID = 1L;

                private String focusedItem;

                @Override
                protected void populateItem(final LoopItem item) {
                    final WebTabHolder holder = allTabs.get(item.getIteration());
                    MarkupContainer link = null;
                    link = new ServoySubmitLink("tablink", useAJAX) //$NON-NLS-1$
                    {
                        private static final long serialVersionUID = 1L;

                        /**
                         * @see wicket.ajax.markup.html.AjaxFallbackLink#onClick(wicket.ajax.AjaxRequestTarget)
                         */
                        @Override
                        public void onClick(AjaxRequestTarget target) {
                            Page page = findPage();
                            if (page != null) {
                                setActiveTabPanel(holder.getPanel());
                                if (target != null) {
                                    relinkAtTabPanel(WebTabPanel.this);
                                    focusedItem = item.getId();
                                    WebEventExecutor.generateResponse(target, page);
                                }
                            }
                        }

                        private void relinkAtForm(WebForm form) {
                            form.visitChildren(WebTabPanel.class, new IVisitor<WebTabPanel>() {
                                public Object component(WebTabPanel wtp) {
                                    relinkAtTabPanel(wtp);
                                    return IVisitor.CONTINUE_TRAVERSAL;
                                }
                            });
                        }

                        private void relinkAtTabPanel(WebTabPanel wtp) {
                            wtp.relinkFormIfNeeded();
                            wtp.visitChildren(WebForm.class, new IVisitor<WebForm>() {
                                public Object component(WebForm form) {
                                    relinkAtForm(form);
                                    return IVisitor.CONTINUE_TRAVERSAL;
                                }
                            });
                        }

                        @Override
                        protected void disableLink(final ComponentTag tag) {
                            // if the tag is an anchor proper
                            if (tag.getName().equalsIgnoreCase("a") || tag.getName().equalsIgnoreCase("link") //$NON-NLS-1$//$NON-NLS-2$
                                    || tag.getName().equalsIgnoreCase("area")) //$NON-NLS-1$
                            {
                                // Remove any href from the old link
                                tag.remove("href"); //$NON-NLS-1$
                                tag.remove("onclick"); //$NON-NLS-1$
                            }
                        }

                    };

                    if (item.getId().equals(focusedItem)) {
                        IRequestTarget currentRequestTarget = RequestCycle.get().getRequestTarget();
                        if (currentRequestTarget instanceof AjaxRequestTarget) {
                            ((AjaxRequestTarget) currentRequestTarget).focusComponent(link);
                        }
                        focusedItem = null;
                    }

                    if (holder.getTooltip() != null) {
                        link.setMetaData(TooltipAttributeModifier.TOOLTIP_METADATA, holder.getTooltip());
                    }

                    TabIndexHelper.setUpTabIndexAttributeModifier(link, tabSequenceIndex);
                    link.add(TooltipAttributeModifier.INSTANCE);

                    if (item.getIteration() == 0)
                        link.add(new AttributeModifier("firsttab", true, new Model<Boolean>(Boolean.TRUE))); //$NON-NLS-1$
                    link.setEnabled(holder.isEnabled() && WebTabPanel.this.isEnabled());

                    String text = holder.getText();
                    if (holder.getDisplayedMnemonic() > 0) {
                        final String mnemonic = Character.toString((char) holder.getDisplayedMnemonic());
                        link.add(new SimpleAttributeModifier("accesskey", mnemonic)); //$NON-NLS-1$
                        if (text != null && text.contains(mnemonic) && !HtmlUtils.hasUsefulHtmlContent(text)) {
                            StringBuffer sbBodyText = new StringBuffer(text);
                            int mnemonicIdx = sbBodyText.indexOf(mnemonic);
                            if (mnemonicIdx != -1) {
                                sbBodyText.insert(mnemonicIdx + 1, "</u>"); //$NON-NLS-1$
                                sbBodyText.insert(mnemonicIdx, "<u>"); //$NON-NLS-1$
                                text = sbBodyText.toString();
                            }
                        }
                    }
                    ServoyTabIcon tabIcon = new ServoyTabIcon("icon", holder, scriptable); //$NON-NLS-1$
                    link.add(tabIcon);

                    Label label = new Label("linktext", new Model<String>(text)); //$NON-NLS-1$
                    label.setEscapeModelStrings(false);
                    link.add(label);
                    item.add(link);
                    IModel<String> selectedOrDisabledClass = new AbstractReadOnlyModel<String>() {
                        private static final long serialVersionUID = 1L;

                        @Override
                        public String getObject() {
                            if (!holder.isEnabled() || !WebTabPanel.this.isEnabled()) {
                                if (currentForm == holder.getPanel()) {
                                    return "disabled_selected_tab"; //$NON-NLS-1$
                                }
                                return "disabled_tab"; //$NON-NLS-1$
                            } else {
                                if (currentForm == holder.getPanel()) {
                                    return "selected_tab"; //$NON-NLS-1$
                                }
                                return "deselected_tab"; //$NON-NLS-1$
                            }
                        }
                    };
                    item.add(new AttributeModifier("class", true, selectedOrDisabledClass)); //$NON-NLS-1$
                    label.add(new StyleAppendingModifier(new Model<String>() {
                        private static final long serialVersionUID = 1L;

                        @Override
                        public String getObject() {
                            String style = "white-space: nowrap;"; //$NON-NLS-1$
                            if (foreground != null) {
                                style += " color:" + PersistHelper.createColorString(foreground); //$NON-NLS-1$
                            }
                            if (holder.getIcon() != null) {
                                style += "; padding-left: 3px"; //$NON-NLS-1$
                            }
                            return style;
                        }
                    }));
                }
            });

            // All tab panels get their tabs rearranged after they make it to the browser.
            // On Chrome & Safari the tab rearrangement produces an ugly flicker effect, because
            // initially the tabs are not visible and then they are made visible. By
            // sending the tab as invisible and turning it to visible only after the tabs
            // are arranged, this jumping/flickering effect is gone. However a small delay can now be
            // noticed in Chrome & Safari, which should also be eliminated somehow.
            // The tab panel is set to visible in function "rearrageTabsInTabPanel" from "servoy.js".
            add(new StyleAppendingModifier(new Model<String>() {
                private static final long serialVersionUID = 1L;

                @Override
                public String getObject() {
                    return "visibility: hidden;overflow:hidden"; //$NON-NLS-1$
                }
            }));

            add(new AbstractServoyDefaultAjaxBehavior() {

                @Override
                protected void respond(AjaxRequestTarget target) {
                }

                @Override
                public void renderHead(IHeaderResponse response) {
                    super.renderHead(response);
                    boolean dontRearrangeHere = false;

                    if (!(getRequestCycle().getRequestTarget() instanceof AjaxRequestTarget)
                            && Utils.getAsBoolean(((MainPage) getPage()).getController().getApplication()
                                    .getRuntimeProperties().get("enableAnchors"))) //$NON-NLS-1$
                    {
                        Component parentForm = getParent();
                        while ((parentForm != null) && !(parentForm instanceof WebForm))
                            parentForm = parentForm.getParent();
                        if (parentForm != null) {
                            int anch = ((WebForm) parentForm).getAnchors(WebTabPanel.this.getMarkupId());
                            if (anch != 0 && anch != IAnchorConstants.DEFAULT)
                                dontRearrangeHere = true;
                        }
                    }
                    if (!dontRearrangeHere) {
                        String jsCall = "rearrageTabsInTabPanel('" + WebTabPanel.this.getMarkupId() + "');"; //$NON-NLS-1$ //$NON-NLS-2$
                        // Safari and Konqueror have some problems with the "domready" event, so for those
                        // browsers we'll use the "load" event. Otherwise use "domready", it reduces the flicker
                        // effect when rearranging the tabs.
                        ClientProperties clp = ((WebClientInfo) Session.get().getClientInfo()).getProperties();
                        if (clp.isBrowserKonqueror() || clp.isBrowserSafari())
                            response.renderOnLoadJavascript(jsCall);
                        else
                            response.renderOnDomReadyJavascript(jsCall);
                    }
                }

                @Override
                public boolean isEnabled(Component component) {
                    return WebClientSession.get().useAjax();
                }

            });
        }
        add(StyleAttributeModifierModel.INSTANCE);
        add(TooltipAttributeModifier.INSTANCE);
        this.scriptable = scriptable;
        ((ChangesRecorder) scriptable.getChangesRecorder()).setDefaultBorderAndPadding(null,
                TemplateGenerator.DEFAULT_LABEL_PADDING);
    }

    public final RuntimeTabPanel getScriptObject() {
        return scriptable;
    }

    /**
     * @return the orient
     */
    public int getOrient() {
        return orient;
    }

    public IStylePropertyChanges getStylePropertyChanges() {
        return scriptable.getChangesRecorder();
    }

    private void setActiveTabPanel(WebTabFormLookup fl) {
        if (fl != currentForm) {
            WebTabFormLookup previous = currentForm;

            if (previous != null) {
                int stopped = application.getFoundSetManager().getEditRecordList().stopEditing(false);
                boolean cantStop = stopped != ISaveConstants.STOPPED && stopped != ISaveConstants.AUTO_SAVE_BLOCKED;
                List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>();
                boolean ok = previous.notifyVisible(false, invokeLaterRunnables);
                Utils.invokeLater(application, invokeLaterRunnables);
                if (cantStop || !ok) {
                    return;
                }
            }

            int previousIndex = -1;
            for (int i = 0; i < allTabs.size(); i++) {
                WebTabHolder holder = allTabs.get(i);
                if (holder.getPanel() == previous) {
                    previousIndex = i;
                    break;
                }
            }

            if (previousIndex != -1) {
                final int changedIndex = previousIndex;
                addStateChange(new Change() {
                    @Override
                    public void undo() {
                        if (allTabs.size() > changedIndex) {
                            WebTabHolder holder = allTabs.get(changedIndex);
                            setActiveTabPanel(holder.getPanel());
                        }
                    }
                });
            }

            List<Runnable> invokeLaterRunnables2 = new ArrayList<Runnable>();
            setCurrentForm(fl, previousIndex, invokeLaterRunnables2);
            Utils.invokeLater(application, invokeLaterRunnables2);
        }
    }

    /**
     * @param fl
     * @param previousIndex
     */
    private void setCurrentForm(WebTabFormLookup fl, int previousIndex, List<Runnable> invokeLaterRunnables) {
        if (fl != null && !fl.isFormReady())
            return;

        getStylePropertyChanges().setChanged();
        currentForm = fl;
        if (parentData != null) {
            showFoundSet(currentForm, parentData, getDefaultSort());
        }

        // Test if current one is there
        if (currentForm.isReady()) {
            WebForm webForm = currentForm.getWebForm();
            if (WebTabPanel.this.get(webForm.getId()) != null) {
                // replace it
                WebTabPanel.this.replace(webForm);
            } else {
                // else add it
                WebTabPanel.this.add(webForm);
            }
            recomputeTabSequence();
            boolean visible = true;
            WebForm webform = findParent(WebForm.class);
            if (webform != null) {
                visible = webform.getController().isFormVisible();
            }
            currentForm.notifyVisible(visible, invokeLaterRunnables);

            if (onTabChangeMethodCmd != null && previousIndex != -1) {
                scriptExecutor.executeFunction(onTabChangeMethodCmd,
                        Utils.arrayMerge((new Object[] { Integer.valueOf(previousIndex + 1) }), onTabChangeArgs),
                        true, this, false, StaticContentSpecLoader.PROPERTY_ONCHANGEMETHODID.getPropertyName(),
                        false);
            }
        }
    }

    public WebForm getCurrentForm() {
        return currentForm != null ? currentForm.getWebForm() : null;
    }

    public IFormUI[] getChildForms() {
        WebForm form = getCurrentForm();
        if (form != null && form.getParent() == null) {
            form = null;
        }
        return form != null ? new IFormUI[] { form } : null;
    }

    /**
     * @see org.apache.wicket.MarkupContainer#remove(org.apache.wicket.Component)
     */
    @Override
    public void remove(Component component) {
        if (currentForm != null && currentForm.isReady() && component == currentForm.getWebForm()) {
            currentForm.setWebForm(null);
            //replace(new Label("webform", new Model<String>("")));
        }
        super.remove(component);
    }

    public void recomputeTabSequence() {
        FormController fc = currentForm.getWebForm().getController();
        fc.recomputeTabSequence(tabSequenceIndex);
    }

    public boolean isCurrentForm(IFormUI formUI) {
        return getCurrentForm() == formUI;
    }

    /**
     * @see wicket.MarkupContainer#onRender(wicket.markup.MarkupStream)
     */
    @Override
    protected void onRender(MarkupStream markupStream) {
        super.onRender(markupStream);
        getStylePropertyChanges().setRendered();
    }

    /**
     * @return
     */
    public void initalizeFirstTab() {
        if (currentForm == null && allTabs.size() > 0) {
            WebTabHolder holder = allTabs.get(0);
            List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>();
            setCurrentForm(holder.getPanel(), -1, invokeLaterRunnables);
            Utils.invokeLater(application, invokeLaterRunnables);
        } else if (currentForm != null && currentForm.getWebForm() == null) {
            // webForm was removed from this tabpanel of the current Form (reuse or destroyed)
            List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>();
            setCurrentForm(currentForm, -1, invokeLaterRunnables);
            Utils.invokeLater(application, invokeLaterRunnables);

        }
        return;
    }

    private void relinkFormIfNeeded() {
        if (currentForm != null && isVisibleInHierarchy()
                && (currentForm.getWebForm() == null || currentForm.getWebForm().getParent() != this)) {
            if (currentForm.getWebForm() == null) {
                if (size() == 0) {
                    // probably current form was destroyed from js code
                    WebTabPanel.this.add(new Label("webform", new Model<String>("")));
                }
            } else if (get(currentForm.getWebForm().getId()) != null) {
                // replace it
                replace(currentForm.getWebForm());
            } else {
                // else add it
                add(currentForm.getWebForm());
            }
        }
    }

    /**
     * @see wicket.Component#onAttach()
     */
    @Override
    protected void onBeforeRender() {
        if (orient != TabPanel.SPLIT_HORIZONTAL && orient != TabPanel.SPLIT_VERTICAL) {
            //tab has to be initialized now.. see also MainPage.listview.onBeforRender..
            initalizeFirstTab();
            super.onBeforeRender();
            relinkFormIfNeeded();
        } else
            super.onBeforeRender();

    }

    public void setTabLayoutPolicy(int scroll_tab_layout) {
        //TODO ignore???
    }

    public IFormLookupPanel createFormLookupPanel(String tabname, String relationName, String formName) {
        return new WebTabFormLookup(tabname, relationName, formName, this, application);
    }

    public void setCursor(Cursor cursor) {
    }

    public void setValidationEnabled(boolean b) {
    }

    public void notifyVisible(boolean visible, List<Runnable> invokeLaterRunnables) {
        if (currentForm == null && allTabs.size() > 0) {
            WebTabHolder holder = allTabs.get(0);
            setCurrentForm(holder.getPanel(), -1, invokeLaterRunnables);
        }
        if (currentForm != null && currentForm.getWebForm() != null) {
            FormController controller = currentForm.getWebForm().getController();

            //this is not needed when closing
            if (visible && parentData != null) {
                showFoundSet(currentForm, parentData, controller.getDefaultSortColumns());

                // Test if current one is there
                if (currentForm.isReady()) {
                    if (WebTabPanel.this.get(currentForm.getWebForm().getId()) != null) {
                        // replace it
                        WebTabPanel.this.replace(currentForm.getWebForm());
                    } else {
                        // else add it
                        WebTabPanel.this.add(currentForm.getWebForm());
                    }
                    recomputeTabSequence();
                }
            }
            controller.notifyVisible(visible, invokeLaterRunnables);
        }
    }

    public void notifyResized() {
        if (currentForm != null && currentForm.isReady()) {
            WebForm webForm = currentForm.getWebForm();
            FormController controller = webForm.getController();
            if (controller != null && webForm.isFormWidthHeightChanged()) {
                controller.notifyResized();
                webForm.clearFormWidthHeightChangedFlag();
            }
        }
    }

    public void setRecord(IRecordInternal parentState, boolean stopEditing) {
        parentData = parentState;
        if (currentForm != null) {

            showFoundSet(currentForm, parentState, getDefaultSort());
        } else if (allTabs.size() > 0) {
            showFoundSet(allTabs.get(0).getPanel(), parentState, getDefaultSort());
        }
        ITagResolver resolver = getTagResolver(parentState);
        for (int i = 0; i < allTabs.size(); i++) {
            WebTabHolder element = allTabs.get(i);
            if (element.refreshTagStrings(resolver)) {
                getStylePropertyChanges().setChanged();
            }
        }
    }

    /**
     * @param parentState
     * @return
     */
    private ITagResolver getTagResolver(IRecordInternal parentState) {
        ITagResolver resolver;
        WebForm webForm = findParent(WebForm.class);
        if (webForm != null) {
            resolver = webForm.getController().getTagResolver();
        } else {
            resolver = TagResolver.createResolver(parentState);
        }
        return resolver;
    }

    protected void showFoundSet(WebTabFormLookup flp, IRecordInternal parentState, List<SortColumn> sort) {
        deregisterSelectionListeners();

        if (!flp.isReady())
            return;

        FormController fp = flp.getWebForm().getController();
        if (fp != null && flp.getRelationName() != null) {
            IFoundSetInternal relatedFoundset = parentState == null ? null
                    : parentState.getRelatedFoundSet(flp.getRelationName(), sort);
            registerSelectionListeners(parentState, flp.getRelationName());
            fp.loadData(relatedFoundset, null);
        }

        ITagResolver resolver = getTagResolver(parentState);
        //refresh tab text
        for (int i = 0; i < allTabs.size(); i++) {
            WebTabHolder element = allTabs.get(i);
            if (element.getPanel() == flp) {
                element.refreshTagStrings(resolver);
                break;
            }
        }
    }

    private void registerSelectionListeners(IRecordInternal parentState, String relationName) {
        String[] parts = relationName.split("\\."); //$NON-NLS-1$
        IRecordInternal currentRecord = parentState;
        for (int i = 0; currentRecord != null && i < parts.length - 1; i++) {
            IFoundSetInternal fs = currentRecord.getRelatedFoundSet(parts[i]);
            if (fs instanceof ISwingFoundSet) {
                related.add((ISwingFoundSet) fs);
                ((ISwingFoundSet) fs).getSelectionModel().addListSelectionListener(this);
            }
            currentRecord = (fs == null) ? null : fs.getRecord(fs.getSelectedIndex());
        }
    }

    private void deregisterSelectionListeners() {
        for (ISwingFoundSet fs : related) {
            fs.getSelectionModel().removeListSelectionListener(this);
        }
        related.clear();
    }

    public String getSelectedRelationName() {
        if (currentForm != null) {
            return currentForm.getRelationName();
        }
        return null;
    }

    public String[] getAllRelationNames() {
        String[] retval = new String[allRelationNames.size()];
        for (int i = 0; i < retval.length; i++) {
            Object relationName = allRelationNames.get(i);
            if (relationName != null) {
                retval[i] = relationName.toString();
            }
        }
        return retval;
    }

    public List<SortColumn> getDefaultSort() {
        if (currentForm != null) {
            // extra test, if the current record is null and the form is not ready just return an empty list.
            // record can be null in the destroy, then creating the form doesn't make any sense.
            return currentForm.getDefaultSort(parentData != null || currentForm.isReady());
        } else if (allTabs.size() > 0) {
            WebTabHolder holder = allTabs.get(0);
            return holder.getPanel().getDefaultSort(parentData != null || holder.getPanel().isReady());
        }
        return null;
    }

    public boolean stopUIEditing(boolean looseFocus) {
        if (currentForm != null && currentForm.isReady()) {
            return currentForm.getWebForm().getController().stopUIEditing(true);
        }
        return true;
    }

    public void destroy() {
        deregisterSelectionListeners();

        //TODO should deregister related foundsets??
    }

    /*
     * tab support----------------------------------------------------------------------------
     */
    public void addTab(String text, int iconMediaId, IFormLookupPanel flp, String tip) {
        byte[] iconData = ComponentFactory.loadIcon(application.getFlattenedSolution(), new Integer(iconMediaId));
        insertTab(text, iconData, flp, tip, allTabs.size(), false);
    }

    public void insertTab(String text, byte[] iconData, IFormLookupPanel flp, String tip, int index,
            boolean loaded) {
        allTabs.add(index, new WebTabHolder(text, flp, iconData, tip));
        allRelationNames.add(index, flp.getRelationName());
        getStylePropertyChanges().setChanged();

        if (allTabs.size() == 1 && loaded) {
            // it's the new active one! If the tabPanel is not loaded, don't do this because it will break execution order (it will be done when tabPanel gets shown)
            // (renderers are now being created - forms initialisation not complete, and we shouldn't generate any JS callbacks like notifyVisible() and such which can access these forms)
            setActiveTabPanel((WebTabFormLookup) flp);
        }
    }

    public void setTabForegroundAt(int index, Color fg) {
    }

    public void setTabBackgroundAt(int index, Color bg) {
    }

    public boolean addTab(IForm formController, String formName, String tabname, String tabText, String tabtooltip,
            String iconURL, String fg, String bg, String relationName, RelatedFoundSet relatedFs, int idx) {
        if (formController != null) {
            //to make sure we don't have recursion on adding a tab, to a tabpanel, that is based
            //on the form that the tabpanel is placed on
            WebForm webForm = findParent(WebForm.class);
            if (webForm != null) {
                FormController parentFormController = webForm.getController();
                if (parentFormController != null && parentFormController.equals(formController)) {
                    return false;
                }
            }
        }

        WebTabFormLookup flp = (WebTabFormLookup) createFormLookupPanel(tabname, relationName, formName);
        if (formController != null)
            flp.setReadOnly(formController.isReadOnly());
        FlattenedSolution fl = application.getFlattenedSolution();
        int mediaId = -1;
        if (iconURL != null && !"".equals(iconURL)) {
            Media media = fl.getMedia(iconURL.replaceAll("media:///", ""));
            if (media != null)
                mediaId = media.getID();
            if (mediaId == -1) {
                Debug.warn("Form '" + formController.getName() + "' with tabpanel  '" + this.name
                        + "' has tabicon  for tab '" + tabname + "'in with icon media url : " + iconURL
                        + " not found");
            }
        }

        byte[] iconData = (mediaId == -1 ? null : ComponentFactory.loadIcon(fl, new Integer(mediaId)));

        int count = allTabs.size();
        int tabIndex = idx;
        if (tabIndex == -1 || tabIndex >= count) {
            tabIndex = count;
        }

        insertTab(application.getI18NMessageIfPrefixed(tabText), iconData, flp,
                application.getI18NMessageIfPrefixed(tabtooltip), tabIndex, true);

        if (fg != null)
            setTabForegroundAt(tabIndex, PersistHelper.createColor(fg));
        if (bg != null)
            setTabBackgroundAt(tabIndex, PersistHelper.createColor(bg));

        // TODO is this if really needed? (insertTab might activate the new tab, but loadData based on relationName only; if it
        // doesn't activate... will ever currentForm == flp?)
        // if the relatedFs is based on a different record then parentState, it would be wrong to use it... maybe we should only use the relationName
        // from the relatedFs - which is already in the relationName param
        if (relatedFs != null && currentForm == flp) {
            FormController fp = flp.getWebForm().getController();
            if (fp != null && flp.getRelationName() != null && flp.getRelationName().equals(relationName)) {
                fp.loadData(relatedFs, null);
            }
        }

        return true;
    }

    public int getMaxTabIndex() {
        return allTabs.size() - 1;
    }

    public String getTabFormNameAt(int i) {
        WebTabHolder holder = allTabs.get(i);
        return holder.getPanel().getFormName();
    }

    public int getTabIndex() {
        for (int i = 0; i < allTabs.size(); i++) {
            if (currentForm == null) {
                // no current form set yet, default to first tab
                return 0;
            }
            if (allTabs.get(i).getPanel() == currentForm) {
                return i;
            }
        }
        return -1;
    }

    public String getTabNameAt(int i) {
        WebTabHolder holder = allTabs.get(i);
        return holder.getPanel().getName();
    }

    public String getTabTextAt(int i) {
        WebTabHolder holder = allTabs.get(i);
        return holder.getText();
    }

    public int getMnemonicAt(int i) {
        WebTabHolder holder = allTabs.get(i);
        return holder.getDisplayedMnemonic();
    }

    public void setMnemonicAt(int i, int m) {
        WebTabHolder holder = allTabs.get(i);
        holder.setDisplayedMnemonic(m);
    }

    public boolean isTabEnabledAt(int index) {
        WebTabHolder holder = allTabs.get(index);
        return holder.isEnabled();
    }

    public boolean removeTabAt(int index) {
        WebTabHolder holder = allTabs.get(index);
        List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>();
        boolean ok = holder.getPanel().notifyVisible(false, invokeLaterRunnables);
        Utils.invokeLater(application, invokeLaterRunnables);
        if (!ok) {
            return false;
        }
        allTabs.remove(index);
        if (holder.getPanel() == currentForm) {
            if (allTabs.size() > 0) {
                setActiveTabPanel(allTabs.get(0).getPanel());
            } else {
                //safety
                currentForm = null;
                replace(new Label("webform", new Model<String>("")));
            }
        }
        return true;
    }

    public boolean removeAllTabs() {
        for (int i = 0; i < allTabs.size(); i++) {
            WebTabHolder comp = allTabs.get(i);
            List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>();
            boolean ok = comp.getPanel().notifyVisible(false, invokeLaterRunnables);
            Utils.invokeLater(application, invokeLaterRunnables);
            if (!ok) {
                return false;
            }
        }
        allTabs.clear();
        allRelationNames.clear();

        //safety
        currentForm = null;

        if (WebTabPanel.this.get("webform") != null) //$NON-NLS-1$
        {
            // replace it
            WebTabPanel.this.replace(new Label("webform", new Model<String>("")));//temporary add; //$NON-NLS-1$ //$NON-NLS-2$
        } else {
            // else add it
            WebTabPanel.this.add(new Label("webform", new Model<String>("")));//temporary add; //$NON-NLS-1$ //$NON-NLS-2$
        }
        return true;
    }

    public void setTabEnabledAt(int i, boolean b) {
        WebTabHolder holder = allTabs.get(i);
        holder.setEnabled(b);
    }

    public void setTabIndex(int index) {
        setActiveTabPanel(allTabs.get(index).getPanel());
    }

    public void setTabIndex(String name) {
        for (int i = 0; i < allTabs.size(); i++) {
            WebTabHolder holder = allTabs.get(i);
            if (Utils.stringSafeEquals(holder.getPanel().getName(), name)) {
                setActiveTabPanel(holder.getPanel());
                break;
            }
        }
    }

    public void setTabTextAt(int i, String s) {
        WebTabHolder holder = allTabs.get(i);
        holder.setText(TemplateGenerator.getSafeText(s));
    }

    /*
     * readonly---------------------------------------------------
     */
    public boolean isReadOnly() {
        if (currentForm != null) {
            return currentForm.isReadOnly();
        }
        return false;
    }

    public void setReadOnly(boolean b) {
        for (int i = 0; i < allTabs.size(); i++) {
            WebTabHolder holder = allTabs.get(i);
            holder.getPanel().setReadOnly(b);
        }
    }

    public void setName(String n) {
        name = 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;
    }

    private String tooltip;

    public void setToolTipText(String tooltip) {
        this.tooltip = Utils.stringIsEmpty(tooltip) ? null : tooltip;
    }

    /**
     * @see com.servoy.j2db.ui.IComponent#getToolTipText()
     */
    public String getToolTipText() {
        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;

    public void setForeground(Color cfg) {
        this.foreground = cfg;
    }

    public Color getForeground() {
        return foreground;
    }

    /*
     * visible---------------------------------------------------
     */
    public void setComponentVisible(boolean visible) {
        if (viewable) {
            setVisible(visible);
        }
    }

    public void setComponentEnabled(final boolean b) {
        if (accessible) {
            super.setEnabled(b);
            visitChildren(IComponent.class, new IVisitor<Component>() {
                public Object component(Component component) {
                    if (component instanceof WebForm) {
                        ((WebForm) component).getController().setComponentEnabled(b);
                    } else if (component instanceof IComponent && !(component instanceof MarkupContainer)) {
                        ((IComponent) component).setComponentEnabled(b);
                    } else if (!(component instanceof MarkupContainer)) {
                        component.setEnabled(b);
                    }
                    return CONTINUE_TRAVERSAL;
                }
            });
            getStylePropertyChanges().setChanged();
        }
    }

    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, new Insets(0, 0, 0, 0), 0, null);
        return new Rectangle(location, d);
    }

    /**
     * @see com.servoy.j2db.ui.ISupportWebBounds#getPaddingAndBorder()
     */
    public Insets getPaddingAndBorder() {
        return ((ChangesRecorder) getStylePropertyChanges()).getPaddingAndBorder(size.height, border,
                new Insets(0, 0, 0, 0), 0, null);
    }

    public void setSize(Dimension size) {
        if (this.size != null && currentForm != null && currentForm.isReady()) {
            currentForm.getWebForm().setFormWidth(0);
        }
        this.size = size;
    }

    /**
     * @see com.servoy.j2db.ui.ITabPanel#addScriptExecuter(com.servoy.j2db.IScriptExecuter)
     */
    public void addScriptExecuter(IScriptExecuter executor) {
        this.scriptExecutor = executor;
    }

    /**
     * @see com.servoy.j2db.ui.ITabPanel#setOnTabChangeMethodCmd(java.lang.String, TabPanel)
     */
    public void setOnTabChangeMethodCmd(String onTabChangeMethodCmd, Object[] onTabChangeArgs) {
        this.onTabChangeMethodCmd = onTabChangeMethodCmd;
        this.onTabChangeArgs = onTabChangeArgs;
    }

    public void setTabSequenceIndex(int tabIndex) {
        this.tabSequenceIndex = tabIndex;
    }

    public int getTabSequenceIndex() {
        return tabSequenceIndex;
    }

    /**
     * @param current
     * @return
     */
    public int getTabIndex(WebForm current) {
        if (currentForm != null && currentForm.getWebForm() == current) {
            Object o = scriptable.js_getTabIndex();
            if (o instanceof Integer) {
                if (((Integer) o).intValue() == -1)
                    return -1;
                return ((Integer) o).intValue() - 1;
            }
        }
        for (int i = 0; i < allTabs.size(); i++) {
            WebTabHolder holder = allTabs.get(i);
            if (holder.getPanel().getFormName() == current.getController().getName()) {
                return i;
            }
        }
        return -1;
    }

    public void valueChanged(ListSelectionEvent e) {
        if (parentData != null) {
            showFoundSet(currentForm, parentData, getDefaultSort());
        }
    }

    public void setTabSize(Dimension tabSize) {
        this.tabSize = tabSize;
    }

    public Dimension getTabSize() {
        return tabSize;
    }

    public void setHorizontalAlignment(int alignment) {

    }

    public static class ServoyTabIcon extends Label implements IResourceListener {
        private final WebTabHolder holder;

        public ServoyTabIcon(String id, final WebTabHolder holder, final IRuntimeComponent scriptable) {
            super(id);
            this.holder = holder;
            add(new StyleAppendingModifier(new Model<String>() {
                @SuppressWarnings("nls")
                @Override
                public String getObject() {
                    StringBuilder result = new StringBuilder();
                    if (holder.getIcon() != null) {
                        result.append("width: ").append(holder.getIcon().getWidth()).append("px; height: ")
                                .append(holder.getIcon().getHeight()).append("px");
                        result.append("; background-image: url(");
                        result.append(getResponse()
                                .encodeURL(urlFor(IResourceListener.INTERFACE) + "&r=" + Math.random()));
                        result.append(')');
                        if (!scriptable.isEnabled()) {
                            result.append("; filter:alpha(opacity=50);-moz-opacity:.50;opacity:.50");
                        }
                    } else {
                        result.append("width: 0px; height: 0px");
                    }
                    return result.toString();
                }
            }));
        }

        public void onResourceRequested() {
            if (holder.getIcon() != null) {
                holder.getIcon().onResourceRequested();
            }
        }
    }

    @Override
    protected void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag) {
        if (getBorder() instanceof TitledBorder) {
            getResponse().write(WebBaseButton.getTitledBorderOpenMarkup((TitledBorder) getBorder()));
        }
        super.onComponentTagBody(markupStream, openTag);
        if (getBorder() instanceof TitledBorder) {
            getResponse().write(WebBaseButton.getTitledBorderCloseMarkup());
        }
    }

    public ISupportSimulateBounds getBoundsProvider() {
        return findParent(ISupportSimulateBounds.class);
    }

    @Override
    public void uiRecreated() {
        recomputeTabSequence();
    }
}