/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.smart.dataui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.DefaultFocusTraversalPolicy; import java.awt.Font; import java.net.URL; import java.util.ArrayList; import java.util.List; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.SwingConstants; import javax.swing.ToolTipManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import com.servoy.base.util.ITagResolver; 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.gui.EnableTabPanel; import com.servoy.j2db.persistence.StaticContentSpecLoader; import com.servoy.j2db.persistence.TabPanel; import com.servoy.j2db.smart.SwingForm; import com.servoy.j2db.ui.IDataRenderer; import com.servoy.j2db.ui.IFormLookupPanel; import com.servoy.j2db.ui.ISupportSecuritySettings; import com.servoy.j2db.ui.ITabPanel; import com.servoy.j2db.ui.scripting.AbstractRuntimeTabPaneAlike; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.EnablePanel; import com.servoy.j2db.util.IFocusCycleRoot; import com.servoy.j2db.util.ISupportFocusTransfer; import com.servoy.j2db.util.ITabPaneAlike; import com.servoy.j2db.util.ImageLoader; import com.servoy.j2db.util.PersistHelper; import com.servoy.j2db.util.Text; import com.servoy.j2db.util.Utils; import com.servoy.j2db.util.gui.AutoTransferFocusListener; /** * The Servoy tabpanel * * @author jblok */ public class SpecialTabPanel extends EnablePanel implements IDisplayRelatedData, ChangeListener, ISupportSecuritySettings, ITabPanel, IFocusCycleRoot<Component>, ISupportFocusTransfer, ListSelectionListener { private static final long serialVersionUID = 1L; private final IApplication application; protected IRecordInternal parentData; private FormLookupPanel currentForm;//reference to test if not already showing private ITabPaneAlike enclosingComponent; private final List<String> originalTabText; private final List<String> originalTabTooltip; private final List<String> allRelationNames = new ArrayList<String>(); private final List<ISwingFoundSet> related = new ArrayList<ISwingFoundSet>(); private boolean validationEnabled = true; private IScriptExecuter scriptExecutor; private String onTabChangeMethod; private Object[] onTabChangeArgs; private final List<Component> tabSeqComponentList = new ArrayList<Component>(); private boolean transferFocusBackwards = false; private final AbstractRuntimeTabPaneAlike scriptable; public SpecialTabPanel(IApplication app, AbstractRuntimeTabPaneAlike scriptable, int orient, boolean oneTab) { this(app, scriptable, orient, oneTab, null); } protected SpecialTabPanel(IApplication app, AbstractRuntimeTabPaneAlike scriptable, int orient, boolean oneTab, ITabPaneAlike enclosingComponent) { super(); application = app; originalTabText = new ArrayList<String>(); originalTabTooltip = new ArrayList<String>(); setLayout(new BorderLayout()); if (enclosingComponent == null) { if (orient == TabPanel.HIDE || (orient == TabPanel.DEFAULT_ORIENTATION && oneTab)) { this.enclosingComponent = new TablessPanel(application); setFocusTraversalPolicy(ServoyFocusTraversalPolicy.datarenderPolicy); } else if (orient == TabPanel.ACCORDION_PANEL) { this.enclosingComponent = new AccordionPanel(application); ToolTipManager.sharedInstance().registerComponent((JComponent)this.enclosingComponent); setFocusTraversalPolicy(ServoyFocusTraversalPolicy.defaultPolicy); } else { this.enclosingComponent = new TabbedPanel(application); ToolTipManager.sharedInstance().registerComponent((EnableTabPanel)this.enclosingComponent); if (orient == SwingConstants.TOP || orient == SwingConstants.LEFT || orient == SwingConstants.BOTTOM || orient == SwingConstants.RIGHT) { this.enclosingComponent.setTabPlacement(orient); } setFocusTraversalPolicy(new DefaultFocusTraversalPolicy() { @Override public Component getComponentBefore(Container aContainer, Component aComponent) { if (!(aComponent instanceof TabbedPanel)) { return super.getComponentBefore(aContainer, aComponent); } else { // go out of this tab panel Container focusRoot = aContainer.getFocusCycleRootAncestor(); return ServoyFocusTraversalPolicy.datarenderPolicy.getComponentBefore(focusRoot, aContainer); } } }); } } else this.enclosingComponent = enclosingComponent; add((Component)this.enclosingComponent, BorderLayout.CENTER); this.enclosingComponent.addChangeListener(this); setFocusCycleRoot(true); tabSeqComponentList.add((Component)this.enclosingComponent); addFocusListener(new AutoTransferFocusListener(this, this)); this.scriptable = scriptable; scriptable.setEnclosingComponent((JComponent)this.enclosingComponent); } public final AbstractRuntimeTabPaneAlike getScriptObject() { return scriptable; } /** * @return the enclosingComponent */ public ITabPaneAlike getEnclosingComponent() { return enclosingComponent; } public boolean isTransferFocusBackwards() { return transferFocusBackwards; } public void setTransferFocusBackwards(boolean transferBackwards) { this.transferFocusBackwards = transferBackwards; } /** * @see com.servoy.j2db.ui.ITabPanel#addScriptExecuter(com.servoy.j2db.IScriptExecuter) */ public void addScriptExecuter(IScriptExecuter el) { this.scriptExecutor = el; } public void destroy() { deregisterSelectionListeners(); // should deregister related foundsets?? if (enclosingComponent instanceof EnableTabPanel) { ToolTipManager.sharedInstance().unregisterComponent((EnableTabPanel)enclosingComponent); } } public void setValidationEnabled(boolean validationEnabled) { this.validationEnabled = validationEnabled; } public void notifyVisible(boolean visible, List<Runnable> invokeLaterRunnables) { currentForm = (FormLookupPanel)enclosingComponent.getSelectedComponent(); if (currentForm != null) { if (visible)//this is not needed when closing { FormController fp = currentForm.getFormPanel();//makes sure is visible if (fp != null) { if (parentData != null) { showFoundSet(currentForm, parentData, currentForm.getDefaultSort()); } } } currentForm.notifyVisible(visible, invokeLaterRunnables); } } public void setRecord(IRecordInternal parentState, boolean stopEditing) { parentData = parentState; if (currentForm != null) // enclosingComponent may already point to the next (uninitialised) form, see stateChanged() { showFoundSet(currentForm, parentState, getDefaultSort()); } ITagResolver resolver = getTagResolver(parentState); for (int i = 0; i < originalTabText.size(); i++) { String element = originalTabText.get(i); if (element != null) { enclosingComponent.setTitleAt(i, Text.processTags(element, resolver)); } } for (int i = 0; i < originalTabTooltip.size(); i++) { String tooltip = originalTabTooltip.get(i); if (tooltip != null) { enclosingComponent.setToolTipTextAt(i, Text.processTags(tooltip, resolver)); } } } protected void showFoundSet(FormLookupPanel flp, IRecordInternal parentState, List<SortColumn> sort) { deregisterSelectionListeners(); if (!flp.isReady()) return; try { FormController fp = flp.getFormPanel(); 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 int i = enclosingComponent.getTabIndex(flp); String element = i < originalTabText.size() ? originalTabText.get(i) : null; if (element != null) { enclosingComponent.setTitleAt(i, Text.processTags(element, resolver)); } String tooltip = i < originalTabTooltip.size() ? originalTabTooltip.get(i) : null; if (tooltip != null) { enclosingComponent.setToolTipTextAt(i, Text.processTags(tooltip, resolver)); } } catch (RuntimeException re) { application.handleException("Error setting the foundset of the relation " + flp.getRelationName() + " on the tab with form " + flp.getFormName(), re); throw re; } } 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(); } /** * @param parentState * @return */ private ITagResolver getTagResolver(IRecordInternal parentState) { ITagResolver resolver; Container parent = getParent(); while (!(parent instanceof SwingForm) && parent != null) { parent = parent.getParent(); } if (parent instanceof SwingForm) { resolver = ((SwingForm)parent).getController().getTagResolver(); } else { resolver = TagResolver.createResolver(parentState); } return resolver; } public String getSelectedRelationName() { if (currentForm != null) // enclosingComponent may already point to the next (uninitialised) form, see stateChanged() { 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) // enclosingComponent may already point to the next (uninitialised) form, see stateChanged() { return currentForm.getDefaultSort(); } return null; } public boolean stopUIEditing(boolean looseFocus) { if (currentForm != null) { return currentForm.stopUIEditing(looseFocus); } return true; } public void stateChanged(ChangeEvent e) { FormLookupPanel flp = (FormLookupPanel)enclosingComponent.getSelectedComponent(); if (currentForm != flp) { //hold a reference so that when calling saveData the lastSelected is not replaced by the current selected. FormLookupPanel previous = currentForm; // stopEditing may trigger calls to this panel, note that currentForm still points to the previous tab // and the new one (enclosingComponent.getSelectedComponent()) may not be initialized yet, // so refer to currentForm and not to enclosingComponent.getSelectedComponent() in callbacks. int stopped = application.getFoundSetManager().getEditRecordList().stopEditing(false); boolean cantStop = stopped != ISaveConstants.STOPPED && stopped != ISaveConstants.AUTO_SAVE_BLOCKED; if (previous != null) { List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>(); boolean ok = previous.notifyVisible(false, invokeLaterRunnables); Utils.invokeLater(application, invokeLaterRunnables); if ((cantStop || !ok)) { currentForm = previous; enclosingComponent.setSelectedComponent(previous); return; } } int previousIndex = enclosingComponent.getTabIndex(previous); currentForm = flp; if (flp != null) { if (parentData != null) { flp.getFormPanel();//make sure the flp is ready showFoundSet(flp, parentData, flp.getDefaultSort()); } List<Runnable> invokeLaterRunnables2 = new ArrayList<Runnable>(); flp.notifyVisible(true, invokeLaterRunnables2); Utils.invokeLater(application, invokeLaterRunnables2); if (onTabChangeMethod != null && previousIndex != -1) { scriptExecutor.executeFunction(onTabChangeMethod, Utils.arrayMerge((new Object[] { Integer.valueOf(previousIndex + 1) }), onTabChangeArgs), true, this, false, StaticContentSpecLoader.PROPERTY_ONCHANGEMETHODID.getPropertyName(), false); } } } } public boolean removeAllTabs() { if (getMaxTabIndex() == -1) return true; boolean retval = false; String tmp = onTabChangeMethod; try { onTabChangeMethod = null; retval = enclosingComponent.removeAllTabs(); if (retval) { originalTabText.clear(); originalTabTooltip.clear(); allRelationNames.clear(); //safety currentForm = null; } } finally { onTabChangeMethod = tmp; } return retval; } public boolean addTab(IForm formController, String formName, String tabname, String tabText, String tooltip, String iconURL, String fg, String bg, String relationName, RelatedFoundSet relatedFs, int idx) { //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 if (formController != null) { Container parent = getParent(); while (!(parent instanceof SwingForm) && parent != null) { parent = parent.getParent(); } if (parent != null) { FormController parentFormController = ((SwingForm)parent).getController(); if (parentFormController != null && parentFormController.equals(formController)) { return false; } } } FormLookupPanel flp = (FormLookupPanel)createFormLookupPanel(tabname, relationName, formName); if (formController != null) flp.setReadOnly(formController.isReadOnly()); Icon icon = null; if (iconURL != null && !"".equals(iconURL)) //$NON-NLS-1$ { try { URL url = new URL(iconURL); icon = new ImageIcon(url); } catch (Exception e) { Debug.error(e); } } int tabIndex = idx; if (tabIndex == -1 || tabIndex >= enclosingComponent.getTabCount()) { tabIndex = enclosingComponent.getTabCount(); addTab(application.getI18NMessageIfPrefixed(tabText), icon, flp, application.getI18NMessageIfPrefixed(tooltip)); } else { insertTab(application.getI18NMessageIfPrefixed(tabText), icon, flp, application.getI18NMessageIfPrefixed(tooltip), tabIndex); } if (fg != null) setTabForegroundAt(tabIndex, PersistHelper.createColor(fg)); if (bg != null) setTabBackgroundAt(tabIndex, PersistHelper.createColor(bg)); if (relatedFs != null && enclosingComponent.getSelectedComponent() == flp) { FormController fp = flp.getFormPanel(); if (fp != null && flp.getRelationName() != null && flp.getRelationName().equals(relationName)) { fp.loadData(relatedFs, null); } } return true; } public void setTabTextAt(int i, String text) { originalTabText.set(i, text); enclosingComponent.setTitleAt(i, text); } public String getTabTextAt(int i) { return enclosingComponent.getTitleAt(i); } public void setMnemonicAt(int i, int m) { enclosingComponent.setMnemonicAt(i, Character.toUpperCase(m)); } public int getMnemonicAt(int i) { return enclosingComponent.getMnemonicAt(i); } public String getTabNameAt(int i) { return enclosingComponent.getNameAt(i); } public String getTabFormNameAt(int i) { return enclosingComponent.getFormNameAt(i); } @Override public void setBackground(Color bg) { super.setBackground(bg); if (enclosingComponent != null) enclosingComponent.setBackground(bg); } @Override public void setForeground(Color fg) { super.setForeground(fg); if (enclosingComponent != null) enclosingComponent.setForeground(fg); } public void setComponentVisible(boolean b_visible) { if (viewable) { setVisible(b_visible); } } /* * readonly--------------------------------------------------- */ public boolean isReadOnly() { return !isEnabled(); } public void setComponentEnabled(final boolean b) { if (accessible) { super.setEnabled(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; } public int getAbsoluteFormLocationY() { Container parent = getParent(); while ((parent != null) && !(parent instanceof IDataRenderer)) { parent = parent.getParent(); } if (parent != null) { return ((IDataRenderer)parent).getYOffset() + getLocation().y; } return getLocation().y; } @Override public void setOpaque(boolean isOpaque) { // always transparent to support semi transparent colors super.setOpaque(false); if (enclosingComponent instanceof JComponent) ((JComponent)enclosingComponent).setOpaque(isOpaque); } public void setTabIndex(int index) { enclosingComponent.setSelectedIndex(index); } public void setTabIndex(String name) { for (int i = 0; i < enclosingComponent.getTabCount(); i++) { if (Utils.stringSafeEquals(enclosingComponent.getNameAt(i), name)) { enclosingComponent.setSelectedIndex(i); break; } } } public int getTabIndex() { return enclosingComponent.getSelectedIndex(); } public int getMaxTabIndex() { return enclosingComponent.getTabCount() - 1; } @Override public void setFont(Font font) { super.setFont(font); if (enclosingComponent != null) enclosingComponent.setFont(font); } public void setTabForegroundAt(int index, Color fg) { enclosingComponent.setForegroundAt(index, fg); } public void setTabBackgroundAt(int index, Color bg) { enclosingComponent.setBackgroundAt(index, bg); } public void setTabEnabledAt(int index, boolean enabled) { enclosingComponent.setEnabledAt(index, enabled); } public boolean isTabEnabledAt(int index) { return enclosingComponent.isEnabledAt(index); } public Color getForegroundAt(int index) { return enclosingComponent.getForegroundAt(index); } public Color getBackgroundAt(int index) { return enclosingComponent.getBackgroundAt(index); } public void addTab(String text, int iconMediaId, IFormLookupPanel flp, String tip) { Icon icon = null; if (iconMediaId > 0) { try { icon = ImageLoader.getIcon(ComponentFactory.loadIcon(application.getFlattenedSolution(), new Integer(iconMediaId)), -1, -1, true); } catch (Exception ex) { Debug.error(ex); } } addTab(text, icon, flp, tip); } public void addTab(String text, Icon icon, IFormLookupPanel flp, String tip) { String tmp = onTabChangeMethod; try { onTabChangeMethod = null; originalTabText.add(((text == null || text.indexOf("%%") == -1) ? null : text)); //$NON-NLS-1$ originalTabTooltip.add(((tip == null || tip.indexOf("%%") == -1) ? null : tip)); //$NON-NLS-1$ allRelationNames.add(flp.getRelationName()); enclosingComponent.addTab(flp.getName(), text, icon, (Component)flp, tip); } finally { onTabChangeMethod = tmp; } } public void insertTab(String text, Icon icon, IFormLookupPanel flp, String tip, int index) { String tmp = onTabChangeMethod; onTabChangeMethod = null; try { originalTabText.add(index, ((text == null || text.indexOf("%%") == -1) ? null : text)); //$NON-NLS-1$ originalTabTooltip.add(((tip == null || tip.indexOf("%%") == -1) ? null : tip)); //$NON-NLS-1$ allRelationNames.add(index, flp.getRelationName()); enclosingComponent.insertTab(flp.getName(), text, icon, (Component)flp, tip, index); } finally { onTabChangeMethod = tmp; } } public boolean removeTabAt(int index) { boolean retval = false; String tmp = onTabChangeMethod; try { onTabChangeMethod = null; retval = enclosingComponent.removeTabAtPos(index); if (retval) { if (index < originalTabText.size()) originalTabText.remove(index); if (index < originalTabTooltip.size()) originalTabTooltip.remove(index); allRelationNames.remove(index); // safety if (allRelationNames.size() == 0) { currentForm = null; } } } finally { onTabChangeMethod = tmp; } return retval; } public void setTabLayoutPolicy(int scroll_tab_layout) { enclosingComponent.setTabLayoutPolicy(scroll_tab_layout); } /** * @see com.servoy.j2db.ui.ITabPanel#setOnTabChangeMethodCmd(int, TabPanel) */ public void setOnTabChangeMethodCmd(String onTabChangeMethod, Object[] onTabChangeArgs) { this.onTabChangeMethod = onTabChangeMethod; this.onTabChangeArgs = onTabChangeArgs; } public IFormLookupPanel createFormLookupPanel(String tabname, String relationName, String formName) { return new FormLookupPanel(application, tabname, relationName, formName); } @Override public String toString() { return "SpecialTabPanel, name='" + getName() + "', hash " + hashCode(); //$NON-NLS-1$ //$NON-NLS-2$ } public String getId() { return (String)getClientProperty("Id"); //$NON-NLS-1$ } public Component getFirstFocusableField() { return (Component)enclosingComponent; } public List<Component> getTabSeqComponents() { return tabSeqComponentList; } public void setTabSeqComponents(List<Component> tabSequence) { // ignore } public boolean isTraversalPolicyEnabled() { return true; } public Component getLastFocusableField() { return (Component)enclosingComponent; } public void valueChanged(ListSelectionEvent e) { if (parentData != null && currentForm != null) // enclosingComponent may already point to the next (uninitialised) form, see stateChanged() { currentForm.getFormPanel();//make sure the flp is ready showFoundSet(currentForm, parentData, currentForm.getDefaultSort()); } } public void setHorizontalAlignment(int alignment) { if (enclosingComponent instanceof AccordionPanel) { ((AccordionPanel)enclosingComponent).setAllTabsAlignment(alignment); } } }