/* 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; import java.applet.Applet; import java.awt.Dimension; import java.awt.Event; import java.awt.Rectangle; import java.awt.print.PrinterJob; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.mozilla.javascript.Context; import org.mozilla.javascript.NativeJavaObject; import org.mozilla.javascript.annotations.JSFunction; import org.mozilla.javascript.annotations.JSGetter; import org.mozilla.javascript.annotations.JSSetter; import com.servoy.base.scripting.api.IJSHistory; import com.servoy.j2db.BasicFormController.JSForm; import com.servoy.j2db.cmd.ICmdManagerInternal; import com.servoy.j2db.dataprocessing.FoundSet; import com.servoy.j2db.dataprocessing.IFoundSetInternal; import com.servoy.j2db.dataprocessing.IRecordInternal; import com.servoy.j2db.dataprocessing.RelatedFoundSet; import com.servoy.j2db.documentation.ServoyDocumented; import com.servoy.j2db.persistence.FlattenedForm; import com.servoy.j2db.persistence.Form; import com.servoy.j2db.persistence.IRepository; import com.servoy.j2db.persistence.ScriptMethod; import com.servoy.j2db.persistence.Solution; import com.servoy.j2db.persistence.SolutionMetaData; import com.servoy.j2db.scripting.IExecutingEnviroment; import com.servoy.j2db.scripting.InstanceJavaMembers; import com.servoy.j2db.scripting.JSWindow; import com.servoy.j2db.scripting.RuntimeWindow; import com.servoy.j2db.scripting.SolutionScope; import com.servoy.j2db.util.AllowNullMap; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.Pair; import com.servoy.j2db.util.ScopesUtils; import com.servoy.j2db.util.UIUtils; import com.servoy.j2db.util.Utils; import com.servoy.j2db.util.gui.AppletController; /** * This class keeps track of all the forms and handles the window menu * * @author jblok, jcompagner */ public abstract class FormManager extends BasicFormManager implements PropertyChangeListener, IFormManagerInternal { public static final String DEFAULT_DIALOG_NAME = "dialog"; //$NON-NLS-1$ public static final Rectangle FULL_SCREEN = new Rectangle(IApplication.FULL_SCREEN, IApplication.FULL_SCREEN, IApplication.FULL_SCREEN, IApplication.FULL_SCREEN); private static final int MAX_FORMS_LOADED; static { // TODO web clients?? Can they also get 160 forms?? long maxMem = Runtime.getRuntime().maxMemory(); if (maxMem > 300000000) { MAX_FORMS_LOADED = 192; } else if (maxMem > 200000000) { MAX_FORMS_LOADED = 128; } else if (maxMem > 100000000) { MAX_FORMS_LOADED = 64; } else { MAX_FORMS_LOADED = 32; } Debug.trace("MaxFormsLoaded set to:" + MAX_FORMS_LOADED); //$NON-NLS-1$ } protected final AllowNullMap<String, IMainContainer> containers; //windowname -> IMainContainer protected IMainContainer currentContainer; protected IMainContainer mainContainer; protected final ConcurrentMap<String, FormController> createdFormControllers; // formName -> FormController protected LinkedList<FormController> leaseHistory; private final AppletController appletContext; //incase we use applets on form private volatile boolean destroyed; /* * _____________________________________________________________ Declaration and definition of constructors */ public FormManager(IApplication app, IMainContainer mainContainer) { super(app); containers = new AllowNullMap<String, IMainContainer>(new ConcurrentHashMap<String, IMainContainer>()); containers.put(mainContainer.getContainerName(), mainContainer); currentContainer = mainContainer; this.mainContainer = mainContainer; leaseHistory = new LinkedList<FormController>(); createdFormControllers = new ConcurrentHashMap<String, FormController>(); appletContext = new AppletController(app); } public IApplication getApplication() { return application; } public void propertyChange(PropertyChangeEvent evt) { String name = evt.getPropertyName(); if ("solution".equals(name)) //$NON-NLS-1$ { final Solution s = (Solution)evt.getNewValue(); destroySolutionSettings();//must run on same thread if (s != null) { application.invokeLater(new Runnable() { public void run() { makeSolutionSettings(s); } }); } else { lastModalContainer = null; } } else if ("mode".equals(name)) //$NON-NLS-1$ { int oldmode = ((Integer)evt.getOldValue()).intValue(); int newmode = ((Integer)evt.getNewValue()).intValue(); handleModeChange(oldmode, newmode); } } /* * _____________________________________________________________ The methods below belong to this class */ public void showSolutionLoading(boolean b) { application.showSolutionLoading(b); } protected abstract void selectFormMenuItem(Form form); public abstract void fillScriptMenu(); protected abstract void enableCmds(boolean enable); protected Form loginForm = null;//as long this is set do not allow to leave this form! //initialize this manager for the solution protected void makeSolutionSettings(Solution s) { destroyed = false; Solution solution = s; boolean isHeadlessClient = application.getApplicationType() == IApplication.HEADLESS_CLIENT; Iterator<Form> e = application.getFlattenedSolution().getForms(true); // add all forms first, they may be referred to in the login form Form first = isHeadlessClient ? null : application.getFlattenedSolution().getForm(solution.getFirstFormID()); boolean firstFormCanBeInstantiated = application.getFlattenedSolution().formCanBeInstantiated(first); while (e.hasNext()) { Form form = e.next(); if (!isHeadlessClient && application.getFlattenedSolution().formCanBeInstantiated(form)) { if (!firstFormCanBeInstantiated) first = form; firstFormCanBeInstantiated = true; } // add anyway, the form may be used in scripting addForm(form, form.equals(first)); } if (!firstFormCanBeInstantiated) { //hmm no forms showSolutionLoading(false); } else { application.getModeManager().setMode(IModeManager.EDIT_MODE);//start in browse mode } if (solution.getLoginFormID() > 0 && solution.getMustAuthenticate() && application.getUserUID() == null) { Form login = application.getFlattenedSolution().getForm(solution.getLoginFormID()); if (application.getFlattenedSolution().formCanBeInstantiated(login) && loginForm == null) { loginForm = login;//must set the login form early so its even correct if onload of login form is called showSolutionLoading(false); showFormInMainPanel(login.getName()); getMainContainer(null).setComponentVisible(true); return; //stop and recall this method from security.login(...)! } } if (solution.getLoginFormID() > 0 && solution.getMustAuthenticate() && application.getUserUID() != null && loginForm != null) { if (currentContainer.getController() != null && loginForm.getName().equals(currentContainer.getController().getForm().getName())) { currentContainer.setController(null); } if (mainContainer.getController() != null && loginForm.getName().equals(mainContainer.getController().getForm().getName())) { mainContainer.setController(null); } loginForm = null;//clear and continue } ScriptMethod sm = null; int modifiers = Utils.getAsInteger(application.getRuntimeProperties().get("load.solution.modifiers")); //$NON-NLS-1$ if ((modifiers & Event.SHIFT_MASK) != Event.SHIFT_MASK) { sm = application.getFlattenedSolution().getScriptMethod(solution.getOnOpenMethodID()); } Object[] solutionOpenMethodArgs = null; String preferedSolutionMethodName = ((ClientState)application).getPreferedSolutionMethodNameToCall(); if (preferedSolutionMethodName == null && ((ClientState)application).getPreferedSolutionMethodArguments() != null) { solutionOpenMethodArgs = ((ClientState)application).getPreferedSolutionMethodArguments(); } if (sm != null) { try { application.getScriptEngine().getScopesScope().executeGlobalFunction(sm.getScopeName(), sm.getName(), Utils.arrayMerge(solutionOpenMethodArgs, Utils.parseJSExpressions(solution.getInstanceMethodArguments("onOpenMethodID"))), false, false); if (application.getSolution() == null) return; } catch (Exception e1) { application.reportError(Messages.getString("servoy.formManager.error.ExecutingOpenSolutionMethod", new Object[] { sm.getName() }), e1); //$NON-NLS-1$ } } showSolutionLoading(false); if (first != null && currentContainer != null && currentContainer.getController() != null && currentContainer.getController().getName().equals(first.getName())) { currentContainer.setController(null); } IMainContainer modalContainer = getModalDialogContainer(); // onOpen event might have opened a modal popup with another form if (first != null && mainContainer.getController() == null) { if (modalContainer != mainContainer) { currentContainer.setComponentVisible(true); setCurrentContainer(modalContainer, null); // if we had a modal dialog displayed, it must remain the current container if (currentContainer.getController() == null) { showFormInCurrentContainer(first.getName()); } else { showFormInMainPanel(first.getName()); //we only set if the solution startup did not yet show a form already } } else { showFormInMainPanel(first.getName()); //we only set if the solution startup did not yet show a form already } } currentContainer.setComponentVisible(true); if (preferedSolutionMethodName != null && application.getFlattenedSolution().isMainSolutionLoaded()) { try { Object[] args = ((ClientState)application).getPreferedSolutionMethodArguments(); // avoid stack overflows when an execute method URL is used to open the solution, and that method does call JSSecurity login ((ClientState)application).resetPreferedSolutionMethodNameToCall(); Pair<String, String> scope = ScopesUtils.getVariableScope(preferedSolutionMethodName); Object result = application.getScriptEngine().getScopesScope().executeGlobalFunction(scope.getLeft(), scope.getRight(), args, false, false); if (application.getSolution().getSolutionType() == SolutionMetaData.AUTHENTICATOR) { application.getRuntimeProperties().put(IServiceProvider.RT_OPEN_METHOD_RESULT, result); } } catch (Exception e1) { application.reportError( Messages.getString("servoy.formManager.error.ExecutingOpenSolutionMethod", new Object[] { preferedSolutionMethodName }), e1); //$NON-NLS-1$ } } } private IMainContainer lastModalContainer; //uninit protected void destroySolutionSettings() { destroyed = true; loginForm = null; try { Iterator<Map.Entry<String, IMainContainer>> it = containers.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, IMainContainer> entry = it.next(); IMainContainer container = entry.getValue(); destroyContainer(container); if (entry.getKey() != null) // remove all none null { it.remove(); } } } catch (Exception e) { Debug.trace(e);//trace not important. } removeAllFormPanels(); currentContainer = getMainContainer(null); leaseHistory = new LinkedList<FormController>(); createdFormControllers.clear(); possibleForms.clear(); } protected void destroyContainer(IMainContainer container) { RuntimeWindow w = application.getRuntimeWindowManager().getWindow(container.getContainerName()); if (w != null) w.destroy(); FormController fc = container.getController(); if (fc != null && fc.isFormVisible()) { fc.getFormUI().setComponentVisible(false); List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>(); fc.notifyVisible(false, invokeLaterRunnables); Utils.invokeLater(application, invokeLaterRunnables); } container.flushCachedItems(); } public abstract IMainContainer getOrCreateMainContainer(String name); public void showFormInDialog(String formName, Rectangle bounds, String title, boolean resizeble, boolean showTextToolbar, boolean closeAll, boolean modal, String windowName) { boolean legacyV3Behavior = false; // first window is modal, second reuses same dialog if (windowName == null) { windowName = DEFAULT_DIALOG_NAME; legacyV3Behavior = true; } RuntimeWindow thisWindow = application.getRuntimeWindowManager().getWindow(windowName); if (thisWindow != null && thisWindow.getType() != JSWindow.DIALOG && thisWindow.getType() != JSWindow.MODAL_DIALOG) { thisWindow.hide(true); // make sure it is closed before reference to it is lost thisWindow.destroy(); thisWindow = null; } if (thisWindow == null) { thisWindow = application.getRuntimeWindowManager().createWindow(windowName, modal ? JSWindow.MODAL_DIALOG : JSWindow.DIALOG, null); } thisWindow.setInitialBounds(bounds.x, bounds.y, bounds.width, bounds.height); thisWindow.showTextToolbar(showTextToolbar); thisWindow.setTitle(title); thisWindow.setResizable(resizeble); thisWindow.oldShow(formName, closeAll, legacyV3Behavior); } public void showFormInFrame(String formName, Rectangle bounds, String windowTitle, boolean resizeble, boolean showTextToolbar, String windowName) { if (windowName == null) { windowName = DEFAULT_DIALOG_NAME; } RuntimeWindow thisWindow = application.getRuntimeWindowManager().getWindow(windowName); if (thisWindow != null && thisWindow.getType() != JSWindow.WINDOW) { thisWindow.hide(true); // make sure it's closed before reference to it is lost thisWindow.destroy(); thisWindow = null; } if (thisWindow == null) { thisWindow = application.getRuntimeWindowManager().createWindow(windowName, JSWindow.WINDOW, null); } thisWindow.setInitialBounds(bounds.x, bounds.y, bounds.width, bounds.height); thisWindow.showTextToolbar(showTextToolbar); thisWindow.setTitle(windowTitle); thisWindow.setResizable(resizeble); thisWindow.oldShow(formName, true, false); // last two params are really not relevant for windows } public IMainContainer getMainContainer(String dialogName) { if (dialogName == null) return mainContainer; return containers.get(dialogName); } public List<String> getCreatedMainContainerKeys() { return new ArrayList<String>(containers.keySet()); } /** * @return */ public IMainContainer getCurrentContainer() { if (currentContainer == null) { return mainContainer; } return currentContainer; } public IMainContainer getModalDialogContainer() { if (lastModalContainer != null) return lastModalContainer; return getCurrentContainer(); } public IMainContainer setModalDialogContainer(IMainContainer container) { IMainContainer previous = lastModalContainer; lastModalContainer = container; return previous; } public FormController showFormInMainPanel(final String formName) { return showFormInMainPanel(formName, getMainContainer(null), null, true, null); } public FormController showFormInCurrentContainer(final String formName) { return showFormInMainPanel(formName, getCurrentContainer(), null, true, application.getRuntimeWindowManager().getCurrentWindowName()); } public void clearLoginForm() { if (application.getSolution().getMustAuthenticate()) makeSolutionSettings(application.getSolution()); } protected boolean design = false; /* * (non-Javadoc) * * @see com.servoy.j2db.IBasicFormManager#showFormInMainPanel(java.lang.String, com.servoy.j2db.IBasicMainContainer, java.lang.Object, boolean, * java.lang.String) */ @Override public IFormController showFormInContainer(String formName, IBasicMainContainer container, String title, boolean closeAll, String dialogName) { return showFormInMainPanel(formName, (IMainContainer)container, title, closeAll, dialogName); } //show a form in the main panel public FormController showFormInMainPanel(final String formName, final IMainContainer container, final String title, final boolean closeAll, final String dialogName) { if (loginForm != null && loginForm.getName() != formName) { return null;//not allowed to leave here...or show anything else than login form } if (formName == null) throw new IllegalArgumentException(application.getI18NMessage("servoy.formManager.error.SettingVoidForm")); //$NON-NLS-1$ FormController currentMainShowingForm = null; if (currentContainer != null) { currentMainShowingForm = container.getController(); } boolean containerSwitch = container != currentContainer; if (currentMainShowingForm != null && formName.equals(currentMainShowingForm.getName()) && !containerSwitch && !design) return leaseFormPanel(currentMainShowingForm.getName()); final Form f = possibleForms.get(formName); if (f == null) { return null; } try { int access = application.getFlattenedSolution().getSecurityAccess(f.getUUID()); if (access != -1) { boolean b_visible = ((access & IRepository.VIEWABLE) != 0); if (!b_visible) { application.invokeLater(new Runnable() { public void run() { application.reportWarningInStatus(application.getI18NMessage("servoy.formManager.warningAccessForm")); //$NON-NLS-1$ } }); return null; } } //handle old panel if (currentMainShowingForm != null) { //leave forms in browse mode // TODO can this be set if notifyVisible returns false (current form is being kept) if (!containerSwitch && application.getModeManager().getMode() != IModeManager.EDIT_MODE) { application.getModeManager().setMode(IModeManager.EDIT_MODE); } FormController fp = leaseFormPanel(currentMainShowingForm.getName()); if (fp != null && !containerSwitch) { List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>(); boolean ok = fp.notifyVisible(false, invokeLaterRunnables); Utils.invokeLater(application, invokeLaterRunnables); // solution closed in onhide method of previous form? if (application.getSolution() == null) return null; if (!ok) { selectFormMenuItem(currentMainShowingForm.getForm()); return fp; } } } //set main FormController tmpForm = currentMainShowingForm; final FormController fp = leaseFormPanel(formName); currentMainShowingForm = fp; currentContainer = container; if (fp != null) { if (application.getCmdManager() instanceof ICmdManagerInternal) { ((ICmdManagerInternal)application.getCmdManager()).setCurrentUndoManager(fp.getUndoManager()); } boolean isNewUser = checkAndUpdateFormUser(fp, container); IFormUIInternal formUI = fp.getFormUI(); if (isNewUser) { container.add(fp.getFormUI(), formName); } if (formUI != null && !formUI.isVisible()) formUI.setComponentVisible(true); // this code must be below the checkAndUpdateUser because setFormController can already set the formui currentContainer.setController(fp); SolutionScope ss = application.getScriptEngine().getSolutionScope(); Context.enter(); try { ss.put("currentcontroller", ss, new NativeJavaObject(ss, fp.initForJSUsage(), new InstanceJavaMembers(ss, JSForm.class))); //$NON-NLS-1$ } finally { Context.exit(); } fp.setView(fp.getView()); fp.executeOnLoadMethod(); // test if solution is closed in the onload method. if (application.getSolution() == null) return null; //correct script menu for this form fillScriptMenu(); // if this is first show and we have to mimic the legacy 3.1 behavior of dialogs (show all in 1 window), allow 100 forms if (!container.isVisible() && !closeAll) { ((FormManager)application.getFormManager()).getHistory(currentContainer).clear(100); } //add to history getHistory(currentContainer).add(fp.getName()); //check for programatic change selectFormMenuItem(f); //show panel as main List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>(); fp.notifyVisible(true, invokeLaterRunnables); // only enable command when it is the default container //if (getMainContainer(null) == currentContainer) // Command should rightly enabled for the forms enableCmds(true); final IMainContainer cachedContainer = currentContainer; Runnable title_focus = new Runnable() { public void run() { FormController fc = cachedContainer.getController(); if (fc != null && fc == fp && application.getSolution() != null) { //correct title String titleText = title; if (titleText == null) titleText = f.getTitleText(); if (NO_TITLE_TEXT.equals(titleText)) titleText = ""; //$NON-NLS-1$ cachedContainer.setTitle(titleText); cachedContainer.requestFocus(); } } }; if (isNewUser) { final IMainContainer showContainer = currentContainer; currentContainer.showBlankPanel();//to overcome paint problem in swing... invokeLaterRunnables.add(new Runnable() { public void run() { // only call show if it is still the right form. FormController currentController = showContainer.getController(); if (currentController != null && fp.getName().equals(currentController.getName())) { showContainer.show(fp.getName()); application.getRuntimeWindowManager().setCurrentWindowName(dialogName); } } }); } else { currentContainer.show(fp.getName()); application.getRuntimeWindowManager().setCurrentWindowName(dialogName); } invokeLaterRunnables.add(title_focus); Utils.invokeLater(application, invokeLaterRunnables); } else { currentContainer.setController(null); } J2DBGlobals.firePropertyChange(this, "form", tmpForm, currentMainShowingForm); //$NON-NLS-1$ return fp; } catch (Exception e) { Debug.error(e); } return null; } public IForm getCurrentForm() { return getCurrentMainShowingFormController(); } public FormController getCurrentMainShowingFormController() { if (currentContainer != null) return currentContainer.getController(); return null; } public boolean isCurrentTheMainContainer() { return getCurrentContainer() == getMainContainer(null); } //get a FormPanelfor use in tabpanes and dialogs public FormController getFormController(String formName, Object parent) { if (formName == null || parent == null) return null; IMainContainer container = currentContainer; if (parent instanceof IMainContainer) container = (IMainContainer)parent; if (container.getController() != null && formName == container.getController().getName()) return null;//savety for circular reference: from A has tabpanel Showing Form B which has tabpanel showing A Form f = possibleForms.get(formName); if (f == null) return null; int access = application.getFlattenedSolution().getSecurityAccess(f.getUUID()); if (access != -1) { boolean b_visible = ((access & IRepository.VIEWABLE) != 0); if (!b_visible) { return null;//user has no access } } FormController fp = leaseFormPanel(formName); if (fp != null) { checkAndUpdateFormUser(fp, parent); fp.initForJSUsage(); fp.setView(fp.getView()); fp.executeOnLoadMethod(); //the form now has to be called to be usable with: //fp.notifyVisible(true); //fp.executeOnShowMethod(); //if (!fp.isShowingData()) fp.loadAllRecordsImpl(true); } return fp; } public void removeAllFormPanels() { FormController fp; boolean hadFormPanels = false; // also first try to get the lock on this, because destroy() call can result in that. // Then a deadlock will happen, so first get it before the leasHistory.. synchronized (this) { synchronized (leaseHistory) { hadFormPanels = leaseHistory.size() > 0; while (leaseHistory.size() > 0) { fp = leaseHistory.removeFirst(); fp.destroy(); } } } if (hadFormPanels) { SolutionScope ss = application.getScriptEngine().getSolutionScope(); if (ss != null) ss.put("currentcontroller", ss, null); //$NON-NLS-1$ } } public void removeFormController(BasicFormController fp) { synchronized (leaseHistory) { leaseHistory.remove(fp); } createdFormControllers.remove(fp.getName()); removeFormUser(fp); } public void touch(FormController fp) { synchronized (leaseHistory) { if (!leaseHistory.isEmpty() && leaseHistory.getLast() != fp) { leaseHistory.remove(fp);//to prevent the panel is added more than once leaseHistory.add(fp); } } } /** * method to comply to interface, internally use leaseForm */ public IForm getForm(String formName) { return leaseFormPanel(formName); } //for non graphical usage only public synchronized FormController leaseFormPanel(String formName) { if (formName == null || destroyed) return null; String name = formName; if (application.getApplicationType() == IApplication.WEB_CLIENT && Utils.getAsBoolean(application.getRuntimeProperties().get("isPrinting"))) //$NON-NLS-1$ { name += "_printing"; //$NON-NLS-1$ } FormController fp = createdFormControllers.get(name); if (fp == null) { Form f = possibleForms.get(formName); if (f == null) return null; try { application.blockGUI(application.getI18NMessage("servoy.formManager.loadingForm") + formName); //$NON-NLS-1$ f = application.getFlattenedSolution().getFlattenedForm(f); fp = new FormController(application, f, name); createdFormControllers.put(fp.getName(), fp); fp.init(); FormController toBeRemoved = null; synchronized (leaseHistory) { int leaseHistorySize = leaseHistory.size(); if (leaseHistorySize > getMaxFormsLoaded()) { for (int i = 0; i < leaseHistorySize; i++) { FormController fc = leaseHistory.get(i); if (canBeDeleted(fc)) { toBeRemoved = fc; break; } } } leaseHistory.remove(fp);//to prevent the panel is added more than once leaseHistory.add(fp); if (Debug.tracing()) { Debug.trace("FormPanel '" + fp.getName() + "' created, Loaded forms: " + leaseHistory.size() + " of " + getMaxFormsLoaded() + " (max)."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ } } if (toBeRemoved != null) { if (Debug.tracing()) { Debug.trace("FormPanel '" + toBeRemoved.getName() + "' removed because of MAX_FORMS_LOADED (" + getMaxFormsLoaded() + //$NON-NLS-1$ //$NON-NLS-2$ ") was passed."); //$NON-NLS-1$ } toBeRemoved.destroy(); } } finally { application.releaseGUI(); } } else { synchronized (leaseHistory) { if (leaseHistory.size() != 0 && leaseHistory.getLast() != fp) { leaseHistory.remove(fp); // move to the end of the list (LRU policy) leaseHistory.add(fp); } } } return fp; } protected int getMaxFormsLoaded() { return MAX_FORMS_LOADED; } /** * Only to be used from printing, use leaseFormPanel or getFormPanel! * * @param f the form to check if there is a controller instance for * @return null if not found */ @Override public synchronized FormController getCachedFormController(String formName) { if (formName == null) return null; FormController fp = createdFormControllers.get(formName); return fp; } protected abstract boolean checkAndUpdateFormUser(FormController fp, Object parentContainer); protected abstract void removeFormUser(BasicFormController fp); public abstract void showPreview(final FormController afp, final IFoundSetInternal foundset, int zoomFactor, final PrinterJob printJob); protected abstract FormController removePreview(); protected void handleModeChange(int oldmode, int newmode) { FormController fp = getCurrentMainShowingFormController(); if (fp != null) { FormController fpBeforePreview = fp; switch (oldmode) { case IModeManager.PREVIEW_MODE : fpBeforePreview = removePreview(); break; case IModeManager.FIND_MODE : // FormController fp = (FormController)leaseFormPanel(f.getName()); fp.setMode(newmode); return; } switch (newmode) { case IModeManager.FIND_MODE : // FormController fp = (FormController)leaseFormPanel(f); fp.setMode(newmode); break; case IModeManager.EDIT_MODE : if (fpBeforePreview != fp) { History history = getCurrentContainer().getHistory(); if (history.getIndex() > 0 && history.getFormName(history.getIndex()).equals(fp.getName())) { history.removeIndex(history.getIndex()); } else if (history.getIndex() > 0 && history.getFormName(history.getIndex()).equals(fpBeforePreview.getName())) { if (history.getLength() > history.getIndex() + 1 && history.getFormName(history.getIndex() + 1).equals(fp.getName())) { history.removeIndex(history.getIndex() + 1); } } else { showFormInCurrentContainer(fpBeforePreview.getName()); } } else { final FormController fp1 = fp; if (fp1 != null) { getCurrentContainer().show(fp1.getName()); List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>(); fp1.notifyVisible(true, invokeLaterRunnables); invokeLaterRunnables.add(new Runnable() { public void run() { fp1.requestFocus(); } }); Utils.invokeLater(application, invokeLaterRunnables); } } break; } } } /** * returns the history of the current container if set. Else it returns the history of the main container. * * @return */ public History getHistory() { return getHistory(null); } public History getHistory(IBasicMainContainer container) { IBasicMainContainer c = container; if (c == null) { if (currentContainer != null) { c = currentContainer; } else { c = getMainContainer(null); } } return c.getHistory(); } @Override public String toString() { // JS toString text! return "History Manager[back,forward,go,length]"; //$NON-NLS-1$ } /** * @see com.servoy.j2db.IManager#flushCachedItems() */ public void flushCachedItems() { //ignore } /** * @see com.servoy.j2db.IManager#init() */ public void init() { //ignore } protected abstract boolean isShowingPrintPreview(); @ServoyDocumented(category = ServoyDocumented.RUNTIME, publicName = "History", scriptingName = "history") public static class HistoryProvider implements IJSHistory { private volatile IApplication application; public HistoryProvider(IApplication app) { application = app; } @Override public String toString() { return "History"; //$NON-NLS-1$ } private IBasicMainContainer getCurrentContainer() { return application.getFormManager().getCurrentContainer(); } /** * Clear the entire history stack. * * @sample history.clear(); */ @JSFunction public void clear() { IBasicMainContainer container = getCurrentContainer(); container.getHistory().clear(); } @JSSetter public void setButtonsEnabled(boolean b) { IBasicMainContainer container = getCurrentContainer(); container.getHistory().setButtonsEnabled(b); } /** * Set/Get the history buttons enabled. * * @sample * history.buttonsEnabled = true; * var status = history.buttonsEnabled; */ @JSGetter public boolean getButtonsEnabled() { IBasicMainContainer container = getCurrentContainer(); return container.getHistory().getButtonsEnabled(); } /** * Get the form name based on the specified absolute index in the history stack location. * * @sample var name = history.getFormName(history.getCurrentIndex()); * @param i the absolute index * @return the formName */ @JSFunction public String getFormName(int i) { IBasicMainContainer container = getCurrentContainer(); return container.getHistory().getFormName(i - 1); // js offset 1 } /** * Navigates to the relative index based on current position in the history. * * @sample history.go(-3); * @param i the relative index */ @JSFunction public void go(int i) { IBasicMainContainer container = getCurrentContainer(); container.getHistory().go(i); } /** * Navigates back in the history stack; shows the previous form (if present). * * @sample history.back(); */ @JSFunction public void back() { // TODO printpreview must be in this container... if (application.getFormManager() instanceof FormManager && ((FormManager)application.getFormManager()).isShowingPrintPreview()) { application.getModeManager().setMode(IModeManager.EDIT_MODE); } else { go(-1); } } /** * Navigates forward in the history stack; shows the next form (if present). * * @sample history.forward(); */ @JSFunction public void forward() { go(1); } /** * Returns the total size of the history stack. * * @sample var size = history.size(); * @return the size */ @JSFunction public int size() { IBasicMainContainer container = getCurrentContainer(); return container.getHistory().getLength(); } /** * Get the current absolute index in the history stack. * * @sample var abs_index = history.getCurrentIndex(); * @return the current absolute index */ @JSFunction public int getCurrentIndex() { IBasicMainContainer container = getCurrentContainer(); return container.getHistory().getIndex() + 1; // js offset 1 } /** * Removes an absolute index based history stack form item. * * @sample var done = history.removeIndex(history.getCurrentIndex()+1); * * @param index the index of the form to remove. * * @return true if successful */ @JSFunction public boolean removeIndex(int index) { IBasicMainContainer container = getCurrentContainer(); return container.getHistory().removeIndex(index - 1); // js offset 1 } /** * Removes the named form item from the history stack (and from memory) if not currently shown. * Will return false when the form can't be removed, this can happen in certain situations: * 1> The form is visible, * 2> The form is executing a function (is actively used), * 3> There are references to this form by a global variable/array, * 4> If the form has a separate foundset with edited records that can't be saved (for example autosave is false) * * @sample var done = history.removeForm('mypreviousform'); * * @param formName the name of the form to remove. * * @return true if successful */ @JSFunction public boolean removeForm(String formName) { IBasicMainContainer container = getCurrentContainer(); return container.getHistory().removeForm(formName); } public void destroy() { application = null; } } public abstract IFormUIInternal getFormUI(FormController formController); public void initializeApplet(Applet applet, Dimension initialSize) { try { UIUtils.initializeApplet(appletContext, applet, initialSize); } catch (Throwable e) { //its not made active Debug.error(e); applet.destroy();//call to leave invalid } } /** * There could be one or more forms currently in find mode. Normally you only have one visible set of related forms in find mode at a time. In order to stop * find mode or perform search you need to know the "root" form in each such relation hierarchy. <br> * For example, in case of related forms a -> b -> c -> of which a and b are visible, a would be the form to use for "stop find mode"/"perform search" * operations. In such a relation hierarchy, applying those operations on any other than the root node would result in problems: only a part of those forms * would exit find mode/perform search. (this can lead to future class cast exceptions when trying to get remaining forms out of find mode + user * confusion). * * @return an array containing forms that are the "root" of a form relation hierarchy in find mode. */ public IForm[] getVisibleRootFormsInFind() { ArrayList<IForm> al = new ArrayList<IForm>(); FormController currentForm = getCurrentMainShowingFormController(); if (currentForm != null && ifRootFormInFind(currentForm)) al.add(currentForm); Iterator<FormController> it = createdFormControllers.values().iterator(); while (it.hasNext()) { FormController fc = it.next(); if (fc != currentForm && ifRootFormInFind(fc)) { al.add(fc); } } return al.toArray(new IForm[al.size()]); } private boolean ifRootFormInFind(FormController fc) { boolean result = false; if (fc.isFormVisible() && fc.isInFindMode()) { IFoundSetInternal foundSet = fc.getFoundSet(); if (foundSet instanceof RelatedFoundSet) { // see if any parent foundset is in find mode; if not, then add it as root // form in find mode List<IRecordInternal> parentRecords = ((RelatedFoundSet)foundSet).getParents(); boolean hasParentsInFindMode = false; for (IRecordInternal record : parentRecords) { IFoundSetInternal parentFoundSet = record.getParentFoundSet(); if (parentFoundSet instanceof FoundSet && ((FoundSet)parentFoundSet).isInFindMode()) { hasParentsInFindMode = true; break; } } if (!hasParentsInFindMode) { result = true; } } else { result = true; } } return result; } /** * @param mainContainer */ public void setCurrentContainer(IMainContainer mainContainer, String name) { if (mainContainer != null) { currentContainer = mainContainer; if (name != null) { // reset it in the containers (must be done for the webclient) containers.put(name, mainContainer); } } else { currentContainer = getMainContainer(null); } enableCmds(true); FormController formController = currentContainer.getController(); if (formController != null) { IExecutingEnviroment scriptEngine = application.getScriptEngine(); if (scriptEngine != null) { SolutionScope ss = scriptEngine.getSolutionScope(); Context.enter(); try { ss.put("currentcontroller", ss, new NativeJavaObject(ss, formController.initForJSUsage(), new InstanceJavaMembers(ss, JSForm.class))); //$NON-NLS-1$ } finally { Context.exit(); } } } application.getRuntimeWindowManager().setCurrentWindowName(name); } /** * @param form * @return */ public List<IFormController> getCachedFormControllers(Form form) { ArrayList<IFormController> al = new ArrayList<IFormController>(); for (FormController controller : createdFormControllers.values()) { if (controller.getForm().getName().equals(form.getName())) { al.add(controller); } else if (controller.getForm() instanceof FlattenedForm) { List<Form> formHierarchy = application.getFlattenedSolution().getFormHierarchy(controller.getForm()); if (formHierarchy.contains(form)) { al.add(controller); } } } return al; } /** * @param form * @return */ public List<IFormController> getCachedFormControllers() { return new ArrayList<IFormController>(createdFormControllers.values()); } /** * @param name * @return */ public boolean recreateForm(String name) { Form test = possibleForms.get(name); if (test != null) { // If form found, test if there is a formcontroller alive. FormController fc = getCachedFormController(name); if (fc != null) { fc.recreateUI(); } } return false; } /** * @param name */ public void removeContainer(String name) { containers.remove(name); } }