/*
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.util;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import com.servoy.j2db.FormController;
import com.servoy.j2db.IDataRendererFactory;
import com.servoy.j2db.IFormUIInternal;
import com.servoy.j2db.IProvideTabSequence;
import com.servoy.j2db.component.IDataRendererYPositionComparator;
import com.servoy.j2db.dataprocessing.IDisplay;
import com.servoy.j2db.persistence.Form;
import com.servoy.j2db.persistence.ISupportName;
import com.servoy.j2db.persistence.ISupportTabSeq;
import com.servoy.j2db.persistence.Part;
import com.servoy.j2db.persistence.TabSeqComparator;
import com.servoy.j2db.ui.IComponent;
import com.servoy.j2db.ui.IDataRenderer;
/**
* Helper class for management of tab sequence. Used both for smart and web clients.
*
* @author gerzse
*/
public class TabSequenceHelper<T>
{
/**
* For each data renderer, we hold pairs of (IPersist, IComponent). They are gathered while the data renderers are created, in IDataRendererFactory.
*/
private final SortedMap<IDataRenderer, SortedMap<ISupportTabSeq, T>> abstractTabSequence;
/**
* Here we store an ordered list of pairs (String, IComponent). These are the components that are part of the tab sequence. They are not yet unrolled, that
* is, the ISupplyFocusChilderen are stored here, and not their children. This map is filled either directly by the user, calling setTabSequence(), either
* from the "abstractTabSequence" above. This map is used for retrieving the names in the tab sequence when getTabSequence() is called.
*/
private final LinkedHashMap<T, String> componentsToNames;
/**
* This is an equivalent of "componentsToNames", but we have the names as keys. This will make it quicker to search up a component based on its name. Also
* this means that some components will not make it into this map, if they don't have a name defined.
*/
private final LinkedHashMap<String, T> namesToComponents;
/**
* This is an ordered list of controls that build up the final, runtime tab sequence. ISupplyFocusChilderen are unrolled, that is, here we store their
* children. This list is filled from the "namedTabSequence" above.
*/
private final List<T> runtimeTabSequence;
// The runtime container (SwingForm/WebForm) and the data renderer factory. Used for putting the tab sequence into effect.
private final IFormUIInternal<T> runtimeContainer;
private final IDataRendererFactory<T> dataRendererFactory;
public TabSequenceHelper(IFormUIInternal<T> runtimeContainer, IDataRendererFactory<T> dataRendererFactory)
{
abstractTabSequence = new TreeMap<IDataRenderer, SortedMap<ISupportTabSeq, T>>(IDataRendererYPositionComparator.INSTANCE);
runtimeTabSequence = new ArrayList<T>();
componentsToNames = new LinkedHashMap<T, String>();
namesToComponents = new LinkedHashMap<String, T>();
this.runtimeContainer = runtimeContainer;
this.dataRendererFactory = dataRendererFactory;
}
public void add(IDataRenderer panel, ISupportTabSeq persist, T component)
{
SortedMap<ISupportTabSeq, T> sequenceForPanel = abstractTabSequence.get(panel);
if (sequenceForPanel == null)
{
sequenceForPanel = new TreeMap<ISupportTabSeq, T>(TabSeqComparator.INSTANCE);
abstractTabSequence.put(panel, sequenceForPanel);
}
sequenceForPanel.put(persist, component);
}
public List<T> getRuntimeTabSequence()
{
return runtimeTabSequence;
}
public String[] getNamesInTabSequence()
{
List<String> namesList = new LinkedList<String>();
for (String name : componentsToNames.values())
if (name != null) namesList.add(name);
return namesList.toArray(new String[namesList.size()]);
}
public void setRuntimeTabSequence(Object[] arrayOfElements)
{
if (arrayOfElements == null)
{
return;
}
Object[] elements = arrayOfElements;
if (elements.length == 1)
{
if (elements[0] instanceof Object[])
{
elements = (Object[])elements[0];
}
else if (elements[0] == null)
{
elements = null;
}
}
componentsToNames.clear();
if (elements != null)
{
for (Object element : elements)
{
String name = null;
if (element instanceof IComponent) name = ((IComponent)element).getName();
if (element instanceof ISupplyFocusChildren)
{
Object[] children = ((ISupplyFocusChildren)element).getFocusChildren();
if (children != null && children.length != 0)
{
for (Object child : children)
{
if (child instanceof IComponent) name = ((IComponent)child).getName();
else name = null;
componentsToNames.put((T)child, name);
}
continue;
}
}
componentsToNames.put((T)element, name);
}
}
revertComponentsToNames();
fromNamedToRuntime();
}
public void fromAbstractToNamed()
{
T tableViewToInsert = null;
int largestIndexBeforeBody = -1;
T lastComponentBeforeBody = null;
LinkedHashMap<T, String> componentGroupsByTabIndex = new LinkedHashMap<T, String>();
FormController fc = runtimeContainer.getController();
Form f = fc.getForm();
Iterator<Part> parts = f.getParts();
while (parts.hasNext())
{
Part p = parts.next();
IDataRenderer dataRenderer = fc.getDataRenderers()[p.getPartType()];
if (dataRenderer != null)
{
// If view provides the tab sequence remember it
// Later we will insert it in the tab sequence.
if ((fc.getViewComponent() instanceof IProvideTabSequence) && (p.getPartType() == Part.BODY))
{
tableViewToInsert = (T)dataRenderer;
}
else
{
SortedMap<ISupportTabSeq, T> dataRendererComponents = abstractTabSequence.get(dataRenderer);
if (dataRendererComponents != null)
{
for (ISupportTabSeq supportTabSeq : dataRendererComponents.keySet())
{
if (supportTabSeq.getTabSeq() >= 0)
{
T next = dataRendererComponents.get(supportTabSeq);
String name = null;
if (supportTabSeq instanceof ISupportName) name = ((ISupportName)supportTabSeq).getName();
componentGroupsByTabIndex.put(next, name);
if ((p.getPartType() == Part.HEADER) || (p.getPartType() == Part.TITLE_HEADER) ||
(p.getPartType() == Part.LEADING_GRAND_SUMMARY))
{
if (supportTabSeq.getTabSeq() >= largestIndexBeforeBody)
{
lastComponentBeforeBody = next;
largestIndexBeforeBody = supportTabSeq.getTabSeq();
}
}
}
}
}
}
}
}
componentsToNames.clear();
if ((lastComponentBeforeBody == null) && (tableViewToInsert != null)) componentsToNames.put(tableViewToInsert, null);
for (T o : componentGroupsByTabIndex.keySet())
{
componentsToNames.put(o, componentGroupsByTabIndex.get(o));
if ((tableViewToInsert != null) && (lastComponentBeforeBody != null) && (o.equals(lastComponentBeforeBody))) componentsToNames.put(
tableViewToInsert, null);
}
revertComponentsToNames();
fromNamedToRuntime();
}
public T getComponentByName(String name)
{
T fce = namesToComponents.get(name);
if (fce != null) return fce;
else return null;
}
public T getComponentForFocus(String name, boolean skipReadonly)
{
T component = null;
Iterator<T> iter = componentsToNames.keySet().iterator();
if (name != null)
{
boolean found = false;
while (iter.hasNext())
{
component = iter.next();
String thisName = componentsToNames.get(component);
if ((thisName != null) && thisName.equals(name))
{
found = true;
break;
}
}
if (found)
{
if (skipReadonly && isComponentReadonly(component))
{
boolean gotAGoodOne = false;
while (iter.hasNext())
{
component = iter.next();
if (!isComponentReadonly(component))
{
gotAGoodOne = true;
break;
}
}
if (!gotAGoodOne)
{
iter = componentsToNames.keySet().iterator();
while (iter.hasNext())
{
component = iter.next();
String thisName = componentsToNames.get(component);
if ((thisName == null) || !thisName.equals(name))
{
if (!isComponentReadonly(component))
{
gotAGoodOne = true;
break;
}
}
}
}
if (!gotAGoodOne) component = null;
}
}
else
{
component = null;
}
}
/* If no name provided, just pick the first component (the first non-readonly, if so requested). */
else
{
while (iter.hasNext())
{
component = iter.next();
if (!skipReadonly || !isComponentReadonly(component)) break;
}
if (skipReadonly && isComponentReadonly(component)) component = null;
}
/* If has focus children, extract them. */
if ((component != null) && (component instanceof ISupplyFocusChildren))
{
Object[] children = ((ISupplyFocusChildren)component).getFocusChildren();
if ((children != null) && (children.length > 0)) component = (T)children[0];
}
return component;
}
private boolean isComponentReadonly(T component)
{
boolean isReadonly = false;
if (component instanceof IDisplay) isReadonly = ((IDisplay)component).isReadOnly();
return isReadonly;
}
private void revertComponentsToNames()
{
/* Fill in the "namesToComponents" map. */
namesToComponents.clear();
for (T comp : componentsToNames.keySet())
{
String name = componentsToNames.get(comp);
if (name != null) namesToComponents.put(name, comp);
}
}
private void fromNamedToRuntime()
{
runtimeTabSequence.clear();
for (T component : componentsToNames.keySet())
{
boolean processed = false;
if (component instanceof ISupplyFocusChildren)
{
T[] children = ((ISupplyFocusChildren<T>)component).getFocusChildren();
if (children != null && children.length != 0)
{
for (T element : children)
runtimeTabSequence.add(element);
processed = true;
}
}
if (!processed)
{
runtimeTabSequence.add(component);
}
}
dataRendererFactory.extendTabSequence(runtimeTabSequence, runtimeContainer);
dataRendererFactory.applyTabSequence(runtimeTabSequence, runtimeContainer);
}
public void clear()
{
abstractTabSequence.clear();
runtimeTabSequence.clear();
componentsToNames.clear();
namesToComponents.clear();
}
}