package org.sigmah.shared.dto.element;
/*
* #%L
* Sigmah
* %%
* Copyright (C) 2010 - 2016 URD
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import org.sigmah.client.cache.UserLocalCache;
import org.sigmah.client.dispatch.CommandResultHandler;
import org.sigmah.client.dispatch.DispatchAsync;
import org.sigmah.client.event.EventBus;
import org.sigmah.client.i18n.I18N;
import org.sigmah.client.security.AuthenticationProvider;
import org.sigmah.client.ui.notif.N10N;
import org.sigmah.client.ui.res.icon.IconImageBundle;
import org.sigmah.client.ui.widget.HistoryTokenText;
import org.sigmah.client.ui.widget.HistoryWindow;
import org.sigmah.client.util.ImageProvider;
import org.sigmah.client.ui.widget.form.IterableGroupPanel;
import org.sigmah.client.util.ToStringBuilder;
import org.sigmah.shared.command.GetHistory;
import org.sigmah.shared.command.result.Authentication;
import org.sigmah.shared.command.result.ListResult;
import org.sigmah.shared.command.result.ValueResult;
import org.sigmah.shared.dto.ContactDTO;
import org.sigmah.shared.dto.ProjectDTO;
import org.sigmah.shared.dto.base.AbstractModelDataEntityDTO;
import org.sigmah.shared.dto.element.event.RequiredValueEvent;
import org.sigmah.shared.dto.element.event.RequiredValueHandler;
import org.sigmah.shared.dto.element.event.ValueEvent;
import org.sigmah.shared.dto.element.event.ValueHandler;
import org.sigmah.shared.dto.history.HistoryTokenListDTO;
import org.sigmah.shared.dto.history.HistoryTokenManager;
import org.sigmah.shared.dto.layout.LayoutConstraintDTO;
import org.sigmah.shared.dto.layout.LayoutGroupDTO;
import org.sigmah.shared.dto.orgunit.OrgUnitDTO;
import org.sigmah.shared.dto.profile.PrivacyGroupDTO;
import org.sigmah.shared.dto.referential.AmendmentState;
import org.sigmah.shared.dto.referential.ElementTypeEnum;
import org.sigmah.shared.dto.referential.PrivacyGroupPermissionEnum;
import org.sigmah.shared.file.TransfertManager;
import org.sigmah.shared.util.ProfileUtils;
import com.allen_sauer.gwt.log.client.Log;
import com.extjs.gxt.ui.client.data.BaseModelData;
import com.extjs.gxt.ui.client.event.ButtonEvent;
import com.extjs.gxt.ui.client.event.MenuEvent;
import com.extjs.gxt.ui.client.event.SelectionListener;
import com.extjs.gxt.ui.client.widget.Component;
import com.extjs.gxt.ui.client.widget.form.Field;
import com.extjs.gxt.ui.client.widget.menu.Menu;
import com.extjs.gxt.ui.client.widget.menu.MenuItem;
import com.google.gwt.event.shared.HandlerManager;
import java.util.Collection;
import com.google.gwt.user.client.rpc.AsyncCallback;
import java.util.Collections;
import java.util.Date;
import java.util.Set;
import org.sigmah.client.ui.widget.Loadable;
import org.sigmah.shared.dto.computation.ComputationTriggerDTO;
import org.sigmah.shared.dto.referential.GlobalPermissionEnum;
import org.sigmah.shared.dto.referential.ValueEventChangeType;
import org.sigmah.shared.util.ProjectUtils;
/**
* Abstract flexible element DTO.
*
* @author Denis Colliot (dcolliot@ideia.fr)
*/
public abstract class FlexibleElementDTO extends AbstractModelDataEntityDTO<Integer> implements HistoryTokenManager {
/**
* Serial version UID.
*/
private static final long serialVersionUID = 8520711106031085130L;
public static final int NUMBER_FIELD_WIDTH = 200;
// DTO 'base' attributes keys.
public static final String LABEL = "label";
public static final String CODE = "code";
public static final String VALIDATES = "validates";
public static final String FILLED_IN = "filledIn";
public static final String AMENDABLE = "amendable";
public static final String EXPORTABLE = "exportable";
public static final String GLOBALLY_EXPORTABLE = "globallyExportable";
public static final String HISTORABLE = "historable";
public static final String PRIVACY_GROUP = "privacyGroup";
public static final String GROUP = "group";
public static final String CONTAINER = "container";
public static final String CONSTRAINT = "constraint";
public static final String BANNER = "banner";
public static final String BANNER_POSITION = "bannerPos";
public static final String DISABLED_DATE = "disabledDate";
public static final String CREATION_DATE = "creationDate";
public static final String COMPUTATION_TRIGGERS = "computationTriggers";
// Provided elements.
protected transient HandlerManager handlerManager;
protected transient DispatchAsync dispatch;
protected transient EventBus eventBus;
protected transient AuthenticationProvider authenticationProvider;
protected transient FlexibleElementContainer currentContainerDTO;
protected transient boolean phaseIsEnded;
protected transient TransfertManager transfertManager;
protected transient int preferredWidth;
private transient Menu historyMenu;
protected transient UserLocalCache cache;
protected transient ImageProvider imageProvider;
// If element is part of an iterative group we need this to determine current iteration
private transient IterableGroupPanel tabPanel;
/**
* Sets the dispatch service to be used in the {@link #getElementComponent(ValueResult)} method.
*
* @param dispatch
* The presenter's dispatch service.
*/
public void setService(DispatchAsync dispatch) {
this.dispatch = dispatch;
}
/**
* Sets the event bus to be used by {@link #getElementComponent(ValueResult)}.
*
* @param eventBus
* The presenter's event bus.
*/
public void setEventBus(EventBus eventBus) {
this.eventBus = eventBus;
}
/**
* Sets the authentication provider to be used in the {@link #getElementComponent(ValueResult)} method.
*
* @param authenticationProvider
* The authentication provider.
*/
public void setAuthenticationProvider(AuthenticationProvider authenticationProvider) {
this.authenticationProvider = authenticationProvider;
}
/**
* Sets the current container (not model, but instance) using this flexible element to be used in the
* {@link #getElementComponent(ValueResult)} method.
*
* @param currentContainerDTO
* The current container using this flexible element.
*/
public void setCurrentContainerDTO(FlexibleElementContainer currentContainerDTO) {
this.currentContainerDTO = currentContainerDTO;
}
/**
* Sets the current transfert manager to allow download and upload operations from flexible elements.
*
* @param transfertManager
* The current transfert manager.
*/
public void setTransfertManager(TransfertManager transfertManager) {
this.transfertManager = transfertManager;
}
/**
* Sets the cache to be used in the {@link #getElementComponent(ValueResult)} method.
*
* @param cache
* The cache.
*/
public void setCache(UserLocalCache cache) {
this.cache = cache;
}
public void setImageProvider(ImageProvider imageProvider) {
this.imageProvider = imageProvider;
}
/**
* Method called just before the {@link FlexibleElementDTO#getElementComponent(ValueResult)} method to ensure the
* instantiation of the attributes used by the client-side.
* This method can be override by subclasses.
*/
public void init() {
// Checks preconditions.
assert dispatch != null;
assert authenticationProvider != null;
assert currentContainerDTO != null;
handlerManager = new HandlerManager(this);
}
public Authentication auth() {
return authenticationProvider.get();
}
/**
* Gets the widget of a flexible element with its value.
*
* @param valueResult
* value of the flexible element, or {@code null} to display the element without its value.
* @return the widget corresponding to the flexible element (can be <code>null</code> if the user cannot see this
* element).
*/
public Component getElementComponent(ValueResult valueResult) {
return getComponentWithHistory(valueResult, false, false);
}
/**
* Gets the widget of a flexible element with its value to be displayed in the banner.
*
* @param valueResult
* value of the flexible element, or {@code null} to display the element without its value.
* @return The widget corresponding to the flexible element (can be <code>null</code> if the user cannot see this
* element).
*/
public Component getElementComponentInBanner(ValueResult valueResult) {
return getComponentWithHistory(valueResult, false, true);
}
/**
* Gets the widget of a flexible element with its value.
*
* @param valueResult
* value of the flexible element, or {@code null} to display the element without its value.
* @param phaseIsEnded
* If the component is enabled.
* @return The widget corresponding to the flexible element (can be <code>null</code> if the user cannot see this
* element).
*/
public Component getElementComponent(ValueResult valueResult, boolean phaseIsEnded) {
return getComponentWithHistory(valueResult, phaseIsEnded, false);
}
/**
* Gets the widget of a flexible element with its value. This method manages the history of the element.
*
* @param valueResult
* value of the flexible element, or {@code null} to display the element without its value.
* @param phaseIsEnded
* If the component is enabled.
* @param inBanner
* If the component will be displayed in the banner.
* @return The widget component.
*/
private Component getComponentWithHistory(ValueResult valueResult, boolean phaseIsEnded, boolean inBanner) {
if (getPermission() == PrivacyGroupPermissionEnum.NONE) {
return null;
}
this.phaseIsEnded = phaseIsEnded;
// Checking if the user has the right to edit this element.
final boolean enabled = userCanPerformChangeType(ValueEventChangeType.EDIT);
// Building the component.
Component component = inBanner ? getComponentInBanner(valueResult) : getComponent(valueResult, enabled);
// Adding the DNA icon if this field is part of the core version.
if(getAmendable() && component instanceof Field) {
final Field<?> field = (Field<?>)component;
field.setFieldLabel(field.getFieldLabel() + " " + IconImageBundle.ICONS.DNABrownGreen().getHTML());
}
// Adds the history menu if needed.
if (isHistorable() && !(this instanceof BudgetElementDTO)) {
if(inBanner || !(component instanceof Field)) {
createHistoryMenu(component);
} else {
final HistoryWrapper wrapper = new HistoryWrapper((Field<?>)component);
wrapper.getHistoryButton().addSelectionListener(new SelectionListener<ButtonEvent>() {
@Override
public void componentSelected(ButtonEvent ce) {
loadAndShowHistory(wrapper.getHistoryButton());
}
});
component = wrapper;
}
}
return component;
}
private Set<Integer> getOrgUnitIds() {
if (currentContainerDTO instanceof DefaultFlexibleElementContainer) {
return Collections.singleton(((DefaultFlexibleElementContainer) currentContainerDTO).getOrgUnitId());
} else if (currentContainerDTO instanceof ContactDTO) {
return ((ContactDTO) currentContainerDTO).getOrgUnitIds();
} else {
return Collections.emptySet();
}
}
private PrivacyGroupPermissionEnum getPermission() {
if (getOrgUnitIds().contains(null)) {
// only draft projects does not have any org units :
// if you can see the project you can do what you want with it
return PrivacyGroupPermissionEnum.WRITE;
}
PrivacyGroupPermissionEnum permission = PrivacyGroupPermissionEnum.NONE;
for (Integer orgUnitId : getOrgUnitIds()) {
PrivacyGroupPermissionEnum privacyGroupPermission = ProfileUtils.getPermissionForOrgUnit(auth(), orgUnitId, getPrivacyGroup());
switch (privacyGroupPermission) {
case NONE:
break;
case READ:
permission = PrivacyGroupPermissionEnum.READ;
break;
case WRITE:
return PrivacyGroupPermissionEnum.WRITE;
default:
throw new IllegalStateException("Unknown PrivacyGroupPermissionEnum : " + privacyGroupPermission);
}
}
return permission;
}
/**
* Creates the history menu. Displayed when the user right click a flexible
* element.
*
* @param component Where to attach the menu.
*/
private void createHistoryMenu(Component component) {
// Builds the menu.
if (historyMenu == null) {
final MenuItem historyItem = new MenuItem(I18N.CONSTANTS.historyShow(), IconImageBundle.ICONS.history(), new SelectionListener<MenuEvent>() {
@Override
public void componentSelected(MenuEvent ce) {
loadAndShowHistory();
}
});
historyMenu = new Menu();
historyMenu.add(historyItem);
}
// Attaches it to the element.
component.setContextMenu(historyMenu);
}
/**
* Send a GetHistory command to retrieve the history of the current element
* and displays it in a popup.
*
* @param loadables Element to mask during the load of the history.
*/
protected void loadAndShowHistory(Loadable... loadables) {
final Integer iterationId = tabPanel == null ? null : tabPanel.getCurrentIterationId();
dispatch.execute(new GetHistory(getId(), currentContainerDTO.getId(), iterationId), new CommandResultHandler<ListResult<HistoryTokenListDTO>>() {
@Override
public void onCommandFailure(final Throwable e) {
if (Log.isErrorEnabled()) {
Log.error("[execute] The history cannot be fetched.", e);
}
N10N.warn(I18N.CONSTANTS.historyError(), I18N.CONSTANTS.historyErrorDetails());
}
@Override
public void onCommandSuccess(final ListResult<HistoryTokenListDTO> result) {
HistoryWindow.show(result.getList(), FlexibleElementDTO.this);
}
}, loadables);
}
/**
* Gets the widget of a flexible element with its value to be displayed in the banner. The default implementation uses
* the {@link #getComponent(ValueResult, boolean)} method.
*
* @param valueResult
* value of the flexible element, or {@code null} to display the element without its value.
* @return the widget corresponding to the flexible element.
*/
protected Component getComponentInBanner(ValueResult valueResult) {
return getComponent(valueResult, false);
}
/**
* Gets the widget of a flexible element with its value.
*
* @param valueResult
* value of the flexible element, or {@code null} to display the element without its value.
* @param enabled
* If the component is enabled.
* @return the widget corresponding to the flexible element.
*/
protected abstract Component getComponent(ValueResult valueResult, boolean enabled);
/**
* Returns <code>true</code> if the current user is allowed to perform
* the given change type.
*
* @param changeType Type of change to verify.
* @return <code>true</code> if the current user can perform the given
* <code>changeType</code>, <code>false</code> otherwise.
*/
protected boolean userCanPerformChangeType(ValueEventChangeType changeType) {
final PrivacyGroupPermissionEnum permission = getPermission();
if(permission == PrivacyGroupPermissionEnum.READ) {
return false;
} else if(permission == PrivacyGroupPermissionEnum.WRITE) {
if(currentContainerDTO instanceof ProjectDTO) {
return userCanPerformChangeTypeOnProject(changeType, (ProjectDTO)currentContainerDTO);
} else if(currentContainerDTO instanceof OrgUnitDTO) {
return userCanPerformChangeTypeOnOrgUnit(changeType, (OrgUnitDTO)currentContainerDTO);
} else if (currentContainerDTO instanceof ContactDTO) {
return userCanPerformChangeTypeOnContact(changeType, (ContactDTO) currentContainerDTO);
}
}
return false;
}
/**
* Returns <code>true</code> if the current user is allowed to perform
* the given change type on the given project.
*
* The default implementation checks for the
* {@link GlobalPermissionEnum#EDIT_ALL_PROJECTS} right and to the current
* amendment state.
*
* @param changeType Type of change to verify.
* @param project Project containing this element.
* @return <code>true</code> if the current user can perform the given
* <code>changeType</code>, <code>false</code> otherwise.
*/
protected boolean userCanPerformChangeTypeOnProject(ValueEventChangeType changeType, ProjectDTO project) {
// No modifications are possible if the model is under maintenance or
// if the user is consulting a previous core version.
if(project.getProjectModel().isUnderMaintenance() || project.getCurrentAmendment() != null) {
return false;
}
// Special case for users with the MODIFY_LOCKED_CONTENT permission.
if(ProfileUtils.isGranted(auth(), GlobalPermissionEnum.MODIFY_LOCKED_CONTENT) &&
(phaseIsEnded || project.isClosed() || project.getAmendmentState() == AmendmentState.LOCKED)) {
// #818: MODIFY_LOCKED_CONTENT can now delete values.
return true;
}
return
// Current phase is opened.
!phaseIsEnded &&
// Current project is opened.
!project.isClosed() &&
// Check that the user can edit the project
ProjectUtils.isProjectEditable(project, auth()) && (
// This element is not part of the core version
!getAmendable() ||
// OR the core version is in an editable state
project.getAmendmentState() == AmendmentState.DRAFT ||
// OR the user has the right to unlock the project
ProfileUtils.isGranted(auth(), GlobalPermissionEnum.LOCK_PROJECT)
);
}
/**
* Returns <code>true</code> if the current user is allowed to perform
* the given change type on the given organization unit.
*
* For organization units, the default implementation only checks for the
* {@link GlobalPermissionEnum#EDIT_ORG_UNIT} right.
*
* @param changeType Type of change to verify.
* @param orgUnit OrgUnit containing this element.
* @return <code>true</code> if the current user can perform the given
* <code>changeType</code>, <code>false</code> otherwise.
*/
protected boolean userCanPerformChangeTypeOnOrgUnit(ValueEventChangeType changeType, OrgUnitDTO orgUnit) {
return
// The project model is not under maintenance.
!orgUnit.getOrgUnitModel().isUnderMaintenance() &&
// The is granted edit rights on organizational units.
ProfileUtils.isGranted(auth(), GlobalPermissionEnum.EDIT_ORG_UNIT);
}
protected boolean userCanPerformChangeTypeOnContact(ValueEventChangeType changeType, ContactDTO contact) {
// TODO: Verify the future permission EDIT_CONTACT
return !contact.getContactModel().isUnderMaintenance();
}
/**
* Adds a {@link ValueHandler} to the flexible element.
*
* @param handler
* a {@link ValueHandler} object
*/
public void addValueHandler(ValueHandler handler) {
if (handlerManager == null) {
init();
}
handlerManager.addHandler(ValueEvent.getType(), handler);
}
/**
* Adds a {@link RequiredValueHandler} to the flexible element.
*
* @param handler
* a {@link RequiredValueHandler} object
*/
public void addRequiredValueHandler(RequiredValueHandler handler) {
handlerManager.addHandler(RequiredValueEvent.getType(), handler);
}
/**
* Gets the most adapted width to display this component.
*
* @return The preferred width of this element.
*/
public int getPreferredWidth() {
return preferredWidth;
}
/**
* {@inheritDoc}
*/
@Override
protected void appendToString(final ToStringBuilder builder) {
builder.append(LABEL, getLabel());
builder.append(VALIDATES, getValidates());
builder.append(FILLED_IN, isFilledIn());
builder.append(AMENDABLE, getAmendable());
builder.append(EXPORTABLE, getExportable());
builder.append(GLOBALLY_EXPORTABLE, getGloballyExportable());
builder.append(HISTORABLE, isHistorable());
}
/**
* Returns the flexible element's formatted label.<br>
* May be overridden by sub-implementations.
*
* @return The flexible element's formatted label.
*/
public String getFormattedLabel() {
// May be overridden.
return getLabel();
}
// Flexible element label
public String getLabel() {
return get(LABEL);
}
public void setLabel(String label) {
set(LABEL, label);
}
// Flexible element code
public String getCode() {
return get(CODE);
}
public void setCode(String code) {
set(CODE, code);
}
// Flexible element validates
public boolean getValidates() {
final Boolean validates = get(VALIDATES);
return validates != null && validates;
}
public void setValidates(boolean validates) {
set(VALIDATES, validates);
}
public boolean isFilledIn() {
final Boolean filledIn = get(FILLED_IN);
return filledIn != null && filledIn;
}
public void setFilledIn(boolean filledIn) {
set(FILLED_IN, filledIn);
}
public boolean getAmendable() {
final Boolean amendable = get(AMENDABLE);
return amendable != null && amendable;
}
public void setAmendable(boolean amendable) {
set(AMENDABLE, amendable);
}
public boolean getExportable() {
final Boolean exportable = get(EXPORTABLE);
return exportable != null && exportable;
}
public void setExportable(boolean exportable) {
set(EXPORTABLE, exportable);
}
public boolean getGloballyExportable() {
final Boolean globallyExportable = get(GLOBALLY_EXPORTABLE);
return globallyExportable != null && globallyExportable;
}
public void setGloballyExportable(boolean globallyExportable) {
set(GLOBALLY_EXPORTABLE, globallyExportable);
}
public boolean isHistorable() {
final Boolean historable = get(HISTORABLE);
return historable != null && historable;
}
public void setHistorable(boolean historable) {
set(HISTORABLE, historable);
}
public PrivacyGroupDTO getPrivacyGroup() {
return get(PRIVACY_GROUP);
}
public void setPrivacyGroup(PrivacyGroupDTO privacyGroup) {
set(PRIVACY_GROUP, privacyGroup);
}
public Collection<ComputationTriggerDTO> getComputationTriggers() {
return get(COMPUTATION_TRIGGERS);
}
public void setComputationTriggers(Collection<ComputationTriggerDTO> computationTriggers) {
set(COMPUTATION_TRIGGERS, computationTriggers);
}
protected void ensureHistorable() {
if (!isHistorable()) {
throw new IllegalStateException("The current flexible element '" + getClass().getName() + "' #" + getId() + " doesn't manage an history.");
}
}
/**
* Assigns a value to a flexible element.
*
* @param result
* The value.
*/
public void assignValue(ValueResult result) {
setFilledIn(isCorrectRequiredValue(result));
}
/**
* Returns if a value can be considered as a correct required value for this specific flexible element.
*
* @param result
* The value.
* @return If the value can be considered as a correct required value.
*/
public abstract boolean isCorrectRequiredValue(ValueResult result);
@Override
public String getElementLabel() {
return getLabel();
}
@Override
public Object renderHistoryToken(HistoryTokenListDTO token) {
ensureHistorable();
return new HistoryTokenText(token);
}
public String toHTML(String value) {
if(value != null) {
return value;
} else {
return "";
}
}
public ElementTypeEnum getElementType() {
final ElementTypeEnum type;
// INFO: Budget elements are handled like DEFAULT elements.
if (this instanceof TextAreaElementDTO) {
type = ElementTypeEnum.TEXT_AREA;
} else if (this instanceof CheckboxElementDTO) {
type = ElementTypeEnum.CHECKBOX;
} else if (this instanceof ContactListElementDTO) {
type = ElementTypeEnum.CONTACT_LIST;
} else if (this instanceof DefaultFlexibleElementDTO) {
type = ElementTypeEnum.DEFAULT;
} else if (this instanceof DefaultContactFlexibleElementDTO) {
type = ElementTypeEnum.DEFAULT_CONTACT;
} else if (this instanceof FilesListElementDTO) {
type = ElementTypeEnum.FILES_LIST;
} else if (this instanceof IndicatorsListElementDTO) {
type = ElementTypeEnum.INDICATORS;
} else if (this instanceof MessageElementDTO) {
type = ElementTypeEnum.MESSAGE;
} else if (this instanceof QuestionElementDTO) {
type = ElementTypeEnum.QUESTION;
} else if (this instanceof ReportElementDTO) {
type = ElementTypeEnum.REPORT;
} else if (this instanceof ReportListElementDTO) {
type = ElementTypeEnum.REPORT_LIST;
} else if (this instanceof TripletsListElementDTO) {
type = ElementTypeEnum.TRIPLETS;
} else if (this instanceof CoreVersionElementDTO) {
type = ElementTypeEnum.CORE_VERSION;
} else if (this instanceof ComputationElementDTO) {
type = ElementTypeEnum.COMPUTATION;
} else {
throw new UnsupportedOperationException("Type '" + getClass() + "' is not supported.");
}
return type;
}
public LayoutGroupDTO getGroup() {
return get(GROUP);
}
public void setGroup(LayoutGroupDTO group) {
set(GROUP, group);
}
public BaseModelData getContainerModel() {
return get(CONTAINER);
}
public void setContainerModel(BaseModelData model) {
set(CONTAINER, model);
}
public LayoutConstraintDTO getConstraint() {
return get(CONSTRAINT);
}
public void setConstraint(LayoutConstraintDTO constraint) {
set(CONSTRAINT, constraint);
}
public LayoutConstraintDTO getBannerConstraint() {
return get(BANNER);
}
public void setBannerConstraint(LayoutConstraintDTO constraint) {
set(BANNER, constraint);
}
public Date getDisabledDate() {
return get(DISABLED_DATE);
}
public void setDisabledDate(Date disabledDate) {
set(DISABLED_DATE, disabledDate);
}
public boolean isDisabled() {
return get(DISABLED_DATE) != null;
}
public Date getCreationDate() {
return get(CREATION_DATE);
}
public void setCreationDate(Date creationDate) {
set(CREATION_DATE, creationDate);
}
public void setTabPanel(IterableGroupPanel tabPanel) {
this.tabPanel = tabPanel;
}
public IterableGroupPanel getTabPanel() {
return tabPanel;
}
}