/*
* RHQ Management Platform
* Copyright (C) 2005-2011 Red Hat, Inc.
* All rights reserved.
*
* 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 version 2 of the License.
*
* 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.coregui.client.components.carousel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.gwt.event.dom.client.KeyCodes;
import com.smartgwt.client.data.Criteria;
import com.smartgwt.client.types.Overflow;
import com.smartgwt.client.types.SelectionStyle;
import com.smartgwt.client.types.VerticalAlignment;
import com.smartgwt.client.util.BooleanCallback;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.HTMLFlow;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.Img;
import com.smartgwt.client.widgets.Label;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.events.DoubleClickHandler;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.fields.FormItem;
import com.smartgwt.client.widgets.form.fields.HiddenItem;
import com.smartgwt.client.widgets.form.fields.SelectItem;
import com.smartgwt.client.widgets.form.fields.SpinnerItem;
import com.smartgwt.client.widgets.form.fields.TextItem;
import com.smartgwt.client.widgets.form.fields.events.ChangedEvent;
import com.smartgwt.client.widgets.form.fields.events.ChangedHandler;
import com.smartgwt.client.widgets.form.fields.events.KeyPressEvent;
import com.smartgwt.client.widgets.form.fields.events.KeyPressHandler;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.LayoutSpacer;
import com.smartgwt.client.widgets.menu.IMenuButton;
import com.smartgwt.client.widgets.menu.Menu;
import com.smartgwt.client.widgets.menu.MenuItem;
import com.smartgwt.client.widgets.menu.events.MenuItemClickEvent;
import org.rhq.core.domain.search.SearchSubsystem;
import org.rhq.coregui.client.CoreGUI;
import org.rhq.coregui.client.RefreshableView;
import org.rhq.coregui.client.components.buttons.BackButton;
import org.rhq.coregui.client.components.form.EnhancedSearchBarItem;
import org.rhq.coregui.client.util.enhanced.EnhancedHLayout;
import org.rhq.coregui.client.util.enhanced.EnhancedIButton;
import org.rhq.coregui.client.util.enhanced.EnhancedToolStrip;
import org.rhq.coregui.client.util.enhanced.EnhancedUtility;
import org.rhq.coregui.client.util.enhanced.EnhancedVLayout;
import org.rhq.coregui.client.util.enhanced.EnhancedIButton.ButtonColor;
/**
* Similar to (i.e. originally a copy of) Table but instead of encapsulating a ListGrid, it manages a list of
* {@link CarouselMember}s, offering horizontal presentation of the member canvases, high level filtering, and
* other member-wide handling. See {@link BookmarkableCarousel} for a subclass providing master-detail support.
*
* @author Jay Shaughnessy
*/
@SuppressWarnings("unchecked")
public abstract class Carousel extends EnhancedHLayout implements RefreshableView {
private static final String FILTER_CAROUSEL_START = "CarouselStart";
private static final String FILTER_CAROUSEL_SIZE = "CarouselSize";
private EnhancedVLayout contents;
private HLayout titleLayout;
private Canvas titleComponent;
private HTMLFlow titleCanvas;
private String titleString;
private List<String> titleIcons = new ArrayList<String>();
private BackButton titleBackButton;
private Canvas carouselDetails;
private CarouselFilter filterForm;
private EnhancedHLayout carouselHolder;
private Label carouselInfo;
private boolean showTitle = true;
private boolean showFooter = true;
private boolean showFooterRefresh = true;
private boolean showFilterForm = true;
private Criteria initialCriteria;
private boolean initialCriteriaFixed = true;
private boolean hideSearchBar = false;
private String initialSearchBarSearchText = null;
private List<CarouselActionInfo> carouselActions = new ArrayList<CarouselActionInfo>();
private boolean carouselActionDisableOverride = false;
protected List<Canvas> extraWidgetsAboveFooter = new ArrayList<Canvas>();
protected List<Canvas> extraWidgetsInMainFooter = new ArrayList<Canvas>();
private EnhancedToolStrip footer;
private EnhancedToolStrip footerExtraWidgets;
private EnhancedIButton refreshButton;
private EnhancedIButton nextButton;
private EnhancedIButton previousButton;
private EnhancedIButton widthButton;
// null means no start filter, start with highest
private Integer carouselStartFilter = null;
// null means no end filter, end with lowest
private Integer carouselEndFilter = null;
// null or < 0 indicates no limit to the carousel size. A size limit is always recommended to avoid unbounded view
private Integer carouselSizeFilter = getDefaultCarouselSize();
private boolean carouselUsingFixedWidths = false;
public Carousel() {
this(null, null);
}
public Carousel(String titleString) {
this(titleString, null);
}
public Carousel(String titleString, Criteria criteria) {
super();
setWidth100();
setHeight100();
setOverflow(Overflow.HIDDEN);
this.titleString = titleString;
this.initialCriteria = criteria;
}
/**
* If this returns true, then even if a {@link #getSearchSubsystem() search subsystem}
* is defined by the class, the search bar will not be shown.
*
* @return true if the search bar is to be hidden (default is false)
*/
public boolean getHideSearchBar() {
return this.hideSearchBar;
}
public void setHideSearchBar(boolean flag) {
this.hideSearchBar = flag;
}
public String getInitialSearchBarSearchText() {
return this.initialSearchBarSearchText;
}
public void setInitialSearchBarSearchText(String text) {
this.initialSearchBarSearchText = text;
}
@Override
protected void onInit() {
super.onInit();
contents = new EnhancedVLayout();
contents.setWidth100();
contents.setHeight100();
addMember(contents);
filterForm = new CarouselFilter(this);
/*
* carousel filters and search bar are currently mutually exclusive
*/
if (getSearchSubsystem() == null) {
configureCarouselFilters();
} else {
if (!this.hideSearchBar) {
final EnhancedSearchBarItem searchFilter = new EnhancedSearchBarItem("search", getSearchSubsystem(),
getInitialSearchBarSearchText());
setFilterFormItems(searchFilter);
}
}
carouselHolder = new EnhancedHLayout();
carouselHolder.setOverflow(Overflow.AUTO);
carouselHolder.setWidth100();
contents.addMember(carouselHolder);
}
protected SelectionStyle getDefaultSelectionStyle() {
return SelectionStyle.MULTIPLE;
}
@Override
protected void onDraw() {
try {
super.onDraw();
for (Canvas child : contents.getMembers()) {
contents.removeChild(child);
}
// Title
this.titleCanvas = new HTMLFlow();
setTitleString(this.titleString);
if (showTitle) {
titleLayout = new EnhancedHLayout();
titleLayout.setAutoHeight();
titleLayout.setAlign(VerticalAlignment.BOTTOM);
titleLayout.setMembersMargin(4);
contents.addMember(titleLayout, 0);
}
if (null != carouselDetails) {
contents.addMember(carouselDetails);
}
if (filterForm.hasContent()) {
contents.addMember(filterForm);
}
contents.addMember(carouselHolder);
// Footer
// A second toolstrip that optionally appears before the main footer - it will contain extra widgets.
// This is hidden from view unless extra widgets are actually added to the carousel above the main footer.
this.footerExtraWidgets = new EnhancedToolStrip();
footerExtraWidgets.setPadding(5);
footerExtraWidgets.setWidth100();
footerExtraWidgets.setMembersMargin(15);
footerExtraWidgets.hide();
contents.addMember(footerExtraWidgets);
this.footer = new EnhancedToolStrip();
footer.setPadding(5);
footer.setWidth100();
footer.setMembersMargin(15);
contents.addMember(footer);
// The ListGrid has been created and configured
// Now give subclasses a chance to configure the carousel
configureCarousel();
Label carouselInfo = new Label();
carouselInfo.setWrap(false);
setCarouselInfo(carouselInfo);
if (showTitle) {
drawTitle();
}
if (showFooter) {
drawFooter();
}
} catch (Exception e) {
CoreGUI.getErrorHandler().handleError(MSG.view_table_drawFail(this.toString()), e);
}
}
@Override
public void destroy() {
EnhancedUtility.destroyMembers(contents);
super.destroy();
}
private void drawTitle() {
for (String headerIcon : titleIcons) {
Img img = new Img(headerIcon, 24, 24);
img.setPadding(4);
titleLayout.addMember(img);
}
if (null != titleBackButton) {
titleLayout.addMember(titleBackButton);
}
titleLayout.addMember(titleCanvas);
if (titleComponent != null) {
titleLayout.addMember(new LayoutSpacer());
titleLayout.addMember(titleComponent);
}
}
private void drawFooter() {
// populate the extraWidgets toolstrip
footerExtraWidgets.removeMembers(footerExtraWidgets.getMembers());
if (!extraWidgetsAboveFooter.isEmpty()) {
for (Canvas extraWidgetCanvas : extraWidgetsAboveFooter) {
footerExtraWidgets.addMember(extraWidgetCanvas);
}
footerExtraWidgets.show();
}
footer.removeMembers(footer.getMembers());
for (final CarouselActionInfo carouselAction : carouselActions) {
if (null == carouselAction.getValueMap()) {
// button action
IButton button = new EnhancedIButton(carouselAction.getTitle());
button.setDisabled(true);
button.setOverflow(Overflow.VISIBLE);
button.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent clickEvent) {
disableAllFooterControls();
if (carouselAction.confirmMessage != null) {
String message = carouselAction.confirmMessage.replaceAll("\\#", "TODO:");
SC.ask(message, new BooleanCallback() {
public void execute(Boolean confirmed) {
if (confirmed) {
carouselAction.action.executeAction(null);
} else {
refreshCarouselInfo();
}
}
});
} else {
carouselAction.action.executeAction(null);
}
}
});
carouselAction.actionCanvas = button;
footer.addMember(button);
} else {
// menu action
Menu menu = new Menu();
final Map<String, ? extends Object> menuEntries = carouselAction.getValueMap();
for (final String key : menuEntries.keySet()) {
MenuItem item = new MenuItem(key);
item.addClickHandler(new com.smartgwt.client.widgets.menu.events.ClickHandler() {
public void onClick(MenuItemClickEvent event) {
disableAllFooterControls();
carouselAction.getAction().executeAction(menuEntries.get(key));
}
});
menu.addItem(item);
}
IMenuButton menuButton = new IMenuButton(carouselAction.getTitle());
menuButton.setMenu(menu);
menuButton.setDisabled(true);
menuButton.setOverflow(Overflow.VISIBLE);
menuButton.setShowMenuBelow(false);
carouselAction.actionCanvas = menuButton;
footer.addMember(menuButton);
}
}
for (Canvas extraWidgetCanvas : extraWidgetsInMainFooter) {
footer.addMember(extraWidgetCanvas);
}
footer.addMember(new LayoutSpacer());
widthButton = new EnhancedIButton(MSG.common_button_fixedWidth());
widthButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent clickEvent) {
carouselUsingFixedWidths = !carouselUsingFixedWidths;
widthButton.setTitle(carouselUsingFixedWidths ? MSG.common_button_scaleToFit() : MSG
.common_button_fixedWidth());
buildCarousel(true);
}
});
footer.addMember(widthButton);
if (isShowFooterRefresh()) {
this.refreshButton = new EnhancedIButton(MSG.common_button_refresh());
refreshButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent clickEvent) {
disableAllFooterControls();
refresh();
}
});
footer.addMember(refreshButton);
}
previousButton = new EnhancedIButton(MSG.common_button_previous());
previousButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent clickEvent) {
disableAllFooterControls();
previous();
}
});
footer.addMember(previousButton);
nextButton = new EnhancedIButton(MSG.common_button_next(), ButtonColor.BLUE);
nextButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent clickEvent) {
disableAllFooterControls();
next();
}
});
footer.addMember(nextButton);
footer.addMember(carouselInfo);
// Ensure buttons are initially set correctly.
refreshCarouselInfo();
}
private void previous() {
if (null == carouselSizeFilter) {
carouselSizeFilter = getDefaultCarouselSize();
}
if (null != carouselStartFilter) {
int newEnd = carouselStartFilter + 1;
setCarouselEndFilter(newEnd);
// it's ok if this is higher than the current max, the actual fetch will make sure the
// values are sane.
setCarouselStartFilter(carouselStartFilter + carouselSizeFilter);
}
buildCarousel(true);
}
private void next() {
if (null == carouselSizeFilter) {
carouselSizeFilter = getDefaultCarouselSize();
}
if (null != carouselEndFilter) {
int newStart = carouselEndFilter - 1;
newStart = (newStart < carouselSizeFilter) ? carouselSizeFilter : newStart;
setCarouselStartFilter(newStart);
}
setCarouselEndFilter(null);
buildCarousel(true);
}
protected abstract int getDefaultCarouselSize();
protected abstract String getCarouselMemberFixedWidth();
protected abstract String getCarouselStartFilterLabel();
protected abstract String getCarouselSizeFilterLabel();
private void disableAllFooterControls() {
for (CarouselActionInfo action : carouselActions) {
action.actionCanvas.disable();
}
for (Canvas extraWidget : extraWidgetsAboveFooter) {
extraWidget.disable();
}
for (Canvas extraWidget : extraWidgetsInMainFooter) {
extraWidget.disable();
}
if (isShowFooterRefresh() && this.refreshButton != null) {
this.refreshButton.disable();
}
}
/**
* Subclasses can use this as a chance to configure the list grid after it has been
* created but before it has been drawn to the DOM. This is also the proper place to add
* actions so that they're rendered in the footer.
*/
protected void configureCarousel() {
return;
}
/**
* If not overriden this will append the standard carousel filters to those already supplied. To ensure
* the supplied form items end a row, call {@link FormItem#setEndRow(Boolean)} as needed. The filter form is
* set to use 6 columns, which allows the carousle filters to fit on one row.
* @param formItems
*/
public void setFilterFormItems(FormItem... formItems) {
// since Arrays.copyOf is unsupported...
FormItem[] carouselFormItems = new FormItem[2 + formItems.length];
int i = 0;
for (FormItem item : formItems) {
carouselFormItems[i++] = item;
}
// drift file path filter
SpinnerItem startFilter = new SpinnerItem(FILTER_CAROUSEL_START, getCarouselStartFilterLabel());
startFilter.setMin(1);
//TextItem startFilter = new TextItem(FILTER_CAROUSEL_START, getCarouselStartFilterLabel());
TextItem numFilter = new TextItem(FILTER_CAROUSEL_SIZE, getCarouselSizeFilterLabel());
carouselFormItems[i++] = startFilter;
carouselFormItems[i++] = numFilter;
this.filterForm.setNumCols(4);
this.filterForm.setItems(carouselFormItems);
}
/**
* Overriding components can use this as a chance to add {@link FormItem}s which will filter
* the carousel members that display their data. If not overriden the standard carousel filters are applied.
*/
protected void configureCarouselFilters() {
setFilterFormItems();
}
/**
* Set the Carousel's title string. This will subsequently call {@link #updateTitleCanvas(String)}.
* @param titleString
*/
public void setTitleString(String titleString) {
this.titleString = titleString;
if (this.titleCanvas != null) {
updateTitleCanvas(titleString);
}
}
public Canvas getTitleCanvas() {
return this.titleCanvas;
}
/**
* To set the Carousel's title, call {@link #setTitleString(String)}. This is primarily declared for purposes of
* override.
* @param titleString
*/
public void updateTitleCanvas(String titleString) {
if (titleString == null) {
titleString = "";
}
if (titleString.length() > 0) {
titleCanvas.setWidth100();
titleCanvas.setHeight(35);
titleCanvas.setContents(titleString);
titleCanvas.setPadding(4);
titleCanvas.setStyleName("HeaderLabel");
} else {
titleCanvas.setWidth100();
titleCanvas.setHeight(0);
titleCanvas.setContents(null);
titleCanvas.setPadding(0);
titleCanvas.setStyleName("normal");
}
titleCanvas.markForRedraw();
}
/**
* Returns the encompassing canvas that contains all content for this component.
* This content includes the carousel members, the buttons, etc.
*/
public Canvas getCarouselContents() {
return this.contents;
}
public boolean isShowTitle() {
return showTitle;
}
public void setShowTitle(boolean showTitle) {
this.showTitle = showTitle;
}
public boolean isShowFooter() {
return showFooter;
}
public void setShowFooter(boolean showFooter) {
this.showFooter = showFooter;
}
protected boolean isInitialCriteriaFixed() {
return initialCriteriaFixed;
}
/**
* @param initialCriteriaFixed If true initialCriteria is applied to all subsequent fetch criteria. If false
* initialCriteria is used only for the initial autoFetch. Irrelevant if autoFetch is false. Default is true.
*/
protected void setInitialCriteriaFixed(boolean initialCriteriaFixed) {
this.initialCriteriaFixed = initialCriteriaFixed;
}
/**
*
* @return the current criteria, which includes any fixed criteria, as well as any user-specified filters; may be
* null if there are no fixed criteria or user-specified filters
*/
protected Criteria getCurrentCriteria() {
Criteria criteria = null;
// If this carousel has a filter form (filters OR search bar),
// we need to refresh it as per the filtering, combined with any fixed criteria.
if (this.filterForm != null && this.filterForm.hasContent()) {
criteria = this.filterForm.getValuesAsCriteria();
if (this.initialCriteriaFixed) {
if (criteria != null) {
if (this.initialCriteria != null) {
// There is fixed criteria - add it to the filter form criteria.
addCriteria(criteria, this.initialCriteria);
}
} else {
criteria = this.initialCriteria;
}
}
} else if (this.initialCriteriaFixed) {
criteria = this.initialCriteria;
}
return criteria;
}
//TODO move to a utility
// SmartGWT 2.4's version of Criteria.addCriteria for some reason doesn't have else clauses for the array types
// and it doesn't handle Object types properly (seeing odd behavior because of this), so this method explicitly
// supports adding array types and Objects.
// This method takes the src criteria and adds it to the dest criteria.
public static void addCriteria(Criteria dest, Criteria src) {
Map otherMap = src.getValues();
Set otherKeys = otherMap.keySet();
for (Iterator i = otherKeys.iterator(); i.hasNext();) {
String field = (String) i.next();
Object value = otherMap.get(field);
if (value instanceof Integer) {
dest.addCriteria(field, (Integer) value);
} else if (value instanceof Float) {
dest.addCriteria(field, (Float) value);
} else if (value instanceof String) {
dest.addCriteria(field, (String) value);
} else if (value instanceof Date) {
dest.addCriteria(field, (Date) value);
} else if (value instanceof Boolean) {
dest.addCriteria(field, (Boolean) value);
} else if (value instanceof Integer[]) {
dest.addCriteria(field, (Integer[]) value);
} else if (value instanceof Double[]) {
dest.addCriteria(field, (Double[]) value);
} else if (value instanceof String[]) {
dest.addCriteria(field, (String[]) value);
} else {
// this is the magic piece - we need to get attrib as an object and set that value
dest.setAttribute(field, src.getAttributeAsObject(field));
}
}
}
public void setCarouselDetails(Canvas carouselDetails) {
this.carouselDetails = carouselDetails;
}
public Canvas getCarouselDetails() {
return carouselDetails;
}
public void setTitleComponent(Canvas canvas) {
this.titleComponent = canvas;
}
/**
* Note: To prevent user action while a current action completes, all widgets on the footer are disabled
* when footer actions take place, typically a button click. It is up to the action to ensure the page
* (via refresh() or CoreGUI.refresh()) or footer (via refreshCarouselActions) are refreshed as needed at action
* completion. Failure to do so may leave the widgets disabled.
*/
public void addCarouselAction(String title, CarouselAction action) {
this.addCarouselAction(title, null, null, action);
}
/**
* Note: To prevent user action while a current action completes, all widgets on the footer are disabled
* when footer actions take place, typically a button click. It is up to the action to ensure the page
* (via refresh() or CoreGUI.refresh()) or footer (via refreshCarouselActions) are refreshed as needed at action
* completion. Failure to do so may leave the widgets disabled.
*/
public void addCarouselAction(String title, String confirmation, CarouselAction action) {
this.addCarouselAction(title, confirmation, null, action);
}
/**
* Note: To prevent user action while a current action completes, all widgets on the footer are disabled
* when footer actions take place, typically a button click. It is up to the action to ensure the page
* (via refresh() or CoreGUI.refresh()) or footer (via refreshCarouselActions) are refreshed as needed at action
* completion. Failure to do so may leave the widgets disabled.
*/
public void addCarouselAction(String title, String confirmation, LinkedHashMap<String, ? extends Object> valueMap,
CarouselAction action) {
CarouselActionInfo info = new CarouselActionInfo(title, confirmation, valueMap, action);
carouselActions.add(info);
}
public void addCarouselMember(Canvas member) {
member.setWidth(carouselUsingFixedWidths ? getCarouselMemberFixedWidth() : "*");
this.carouselHolder.addMember(member);
}
public void setListGridDoubleClickHandler(DoubleClickHandler handler) {
//doubleClickHandler = handler;
}
/**
* Adds extra widgets to the bottom of the carousel view.
* <br/><br/>
* Note: To prevent user action while a current action completes, all widgets on the footer are disabled
* when footer actions take place, typically a button click. It is up to the action to ensure the page
* (via refresh() or CoreGUI.refresh()) or footer (via refreshCarouselActions) are refreshed as needed at action
* completion. Failure to do so may leave the widgets disabled.
*
* @param widget the new widget to add to the view
* @param aboveFooter if true, the widget will be placed in a second toolstrip just above the main footer.
* if false, the widget will be placed in the main footer toolstrip itself. This is
* useful if the widget is really big and won't fit in the main footer along with the
* rest of the main footer members.
*/
public void addExtraWidget(Canvas widget, boolean aboveFooter) {
if (aboveFooter) {
this.extraWidgetsAboveFooter.add(widget);
} else {
this.extraWidgetsInMainFooter.add(widget);
}
}
public void setHeaderIcon(String headerIcon) {
if (this.titleIcons.size() > 0) {
this.titleIcons.clear();
}
addHeaderIcon(headerIcon);
}
public void addHeaderIcon(String headerIcon) {
this.titleIcons.add(headerIcon);
}
public void setTitleBackButton(BackButton backButton) {
this.titleBackButton = backButton;
}
/**
* By default, all actions have buttons that are enabled or
* disabled based on if and how many rows are selected. There are
* times when you don't want the user to be able to press action
* buttons regardless of which rows are selected. This method let's
* you set this override-disable flag.
*
* Note: this also effects the double-click handler - if this disable override
* is on, the double-click handler is not called.
*
* @param disabled if true, all action buttons will be disabled
* if false, action buttons will be enabled based on their predefined
* selection enablement rule.
*/
public void setCarouselActionDisableOverride(boolean disabled) {
this.carouselActionDisableOverride = disabled;
refreshCarouselInfo();
}
public boolean getCarouselActionDisableOverride() {
return this.carouselActionDisableOverride;
}
/**
* Refreshes the members, filtered by any fixed criteria, as well as any user-specified filters.
*/
public void refresh() {
Criteria criteria = getCurrentCriteria();
Map<String, Object> criteriaMap = (criteria != null) ? criteria.getValues() : Collections
.<String, Object> emptyMap();
try {
carouselSizeFilter = Integer.valueOf((String) criteriaMap.get(FILTER_CAROUSEL_SIZE));
} catch (Exception e) {
carouselSizeFilter = null;
}
try {
carouselStartFilter = (Integer) criteriaMap.get(FILTER_CAROUSEL_START);
} catch (Exception e) {
carouselStartFilter = null;
}
// on refresh remove any end filter as the criteria may have changed completely
carouselEndFilter = null;
// Any change to filters means we have to rebuild the carousel because the set of members may change, because
// "empty" members (i.e. members whose relevant data has been completely filtered) may be omitted completely
// from the carousel.
buildCarousel(true);
// TODO: it would be best if this was actually called after the async return of the member refreshes
refreshCarouselInfo();
}
protected void buildCarousel(boolean isRefresh) {
if (null != carouselHolder) {
carouselHolder.destroyMembers();
}
}
public void refreshCarouselInfo() {
if (this.showFooter && (this.carouselHolder != null)) {
if (this.carouselActionDisableOverride) {
//this.listGrid.setSelectionType(SelectionStyle.NONE);
} else {
//this.listGrid.setSelectionType(getDefaultSelectionStyle());
}
//int selectionCount = this.listGrid.getSelectedRecords().length;
for (CarouselActionInfo carouselAction : this.carouselActions) {
if (carouselAction.actionCanvas != null) { // if null, we haven't initialized our buttons yet, so skip this
boolean enabled = (!this.carouselActionDisableOverride && carouselAction.action.isEnabled());
carouselAction.actionCanvas.setDisabled(!enabled);
}
}
for (Canvas extraWidget : this.extraWidgetsAboveFooter) {
extraWidget.enable();
if (extraWidget instanceof CarouselWidget) {
((CarouselWidget) extraWidget).refresh(carouselHolder.getMembers());
}
}
for (Canvas extraWidget : this.extraWidgetsInMainFooter) {
extraWidget.enable();
if (extraWidget instanceof CarouselWidget) {
((CarouselWidget) extraWidget).refresh(carouselHolder.getMembers());
}
}
if (isShowFooterRefresh() && this.refreshButton != null) {
this.refreshButton.enable();
}
}
}
// -------------- Inner utility classes ------------- //
/**
* A subclass of SmartGWT's DynamicForm widget that provides a more convenient interface for filtering a
* {@link Carousel} of data.
*
* @author Joseph Marques
*/
private static class CarouselFilter extends DynamicForm implements KeyPressHandler, ChangedHandler,
com.google.gwt.event.dom.client.KeyPressHandler {
private Carousel carousel;
private EnhancedSearchBarItem searchBarItem;
private HiddenItem hiddenItem;
public CarouselFilter(Carousel carousel) {
super();
setIsGroup(true);
setGroupTitle(MSG.common_label_filters());
setWidth100();
setPadding(5);
this.carousel = carousel;
}
@Override
public void setItems(FormItem... items) {
for (FormItem nextFormItem : items) {
nextFormItem.setWrapTitle(false);
nextFormItem.setWidth(300); // wider than default
if (nextFormItem instanceof TextItem) {
nextFormItem.addKeyPressHandler(this);
} else if (nextFormItem instanceof SelectItem) {
nextFormItem.addChangedHandler(this);
} else if (nextFormItem instanceof EnhancedSearchBarItem) {
//searchBarItem = (SearchBarItem) nextFormItem;
//searchBarItem.getSearchBar().addKeyPressHandler(this);
String name = searchBarItem.getName();
searchBarItem.setName(name + "_hidden");
hiddenItem = new HiddenItem(name);
//hiddenItem.setValue(searchBarItem.getSearchBar().getValue());
}
}
if (hiddenItem != null) {
FormItem[] tmpItems = new FormItem[items.length + 1];
System.arraycopy(items, 0, tmpItems, 0, items.length);
tmpItems[items.length] = hiddenItem;
items = tmpItems;
}
super.setItems(items);
}
private void fetchFilteredCarouselData() {
carousel.refresh();
}
public void onKeyPress(KeyPressEvent event) {
if (event.getKeyName().equals("Enter") == false) {
return;
}
fetchFilteredCarouselData();
}
public void onChanged(ChangedEvent event) {
fetchFilteredCarouselData();
}
public boolean hasContent() {
return super.getFields().length != 0;
}
@Override
public void onKeyPress(com.google.gwt.event.dom.client.KeyPressEvent event) {
if (event.getCharCode() != KeyCodes.KEY_ENTER) {
return;
}
// TODO: figure out why this event is being sent twice
//hiddenItem.setValue(searchBarItem.getSearchBar().getValue());
fetchFilteredCarouselData();
}
}
public static class CarouselActionInfo {
private String title;
private String confirmMessage;
private LinkedHashMap<String, ? extends Object> valueMap;
private CarouselAction action;
private Canvas actionCanvas;
protected CarouselActionInfo(String title, String confirmMessage,
LinkedHashMap<String, ? extends Object> valueMap, CarouselAction action) {
this.title = title;
this.confirmMessage = confirmMessage;
this.valueMap = valueMap;
this.action = action;
}
public String getTitle() {
return title;
}
public String getConfirmMessage() {
return confirmMessage;
}
public LinkedHashMap<String, ? extends Object> getValueMap() {
return valueMap;
}
public Canvas getActionCanvas() {
return actionCanvas;
}
public void setActionCanvas(Canvas actionCanvas) {
this.actionCanvas = actionCanvas;
}
public CarouselAction getAction() {
return action;
}
public void setAction(CarouselAction action) {
this.action = action;
}
}
public boolean isShowFooterRefresh() {
return showFooterRefresh;
}
public void setShowFooterRefresh(boolean showFooterRefresh) {
this.showFooterRefresh = showFooterRefresh;
}
public Label getCarouselInfo() {
return carouselInfo;
}
public void setCarouselInfo(Label carouselInfo) {
this.carouselInfo = carouselInfo;
}
public boolean isShowFilterForm() {
return showFilterForm;
}
public void setShowFilterForm(boolean showFilterForm) {
this.showFilterForm = showFilterForm;
}
/*
* by default, no search bar is shown above this. if this represents a subsystem that is capable
* of search, return the specific object here.
*/
protected SearchSubsystem getSearchSubsystem() {
return null;
}
protected Integer getCarouselStartFilter() {
return carouselStartFilter;
}
protected Integer getCarouselStartFilterMax() {
Double max = ((SpinnerItem) this.filterForm.getItem(FILTER_CAROUSEL_START)).getMax();
return (null == max) ? null : max.intValue();
}
protected void setCarouselStartFilterMax(Integer startFilterMax) {
SpinnerItem spinner = (SpinnerItem) this.filterForm.getItem(FILTER_CAROUSEL_START);
spinner.setMax((Integer) ((null == startFilterMax || startFilterMax < 0) ? null : startFilterMax));
}
protected void setCarouselStartFilter(Integer carouselStartFilter) {
this.carouselStartFilter = carouselStartFilter;
if (null != carouselStartFilter) {
this.filterForm.getItem(FILTER_CAROUSEL_START).setValue(String.valueOf(carouselStartFilter));
this.filterForm.getItem(FILTER_CAROUSEL_START).redraw();
}
}
protected Integer getCarouselEndFilter() {
return carouselEndFilter;
}
protected void setCarouselEndFilter(Integer carouselEndFilter) {
this.carouselEndFilter = carouselEndFilter;
}
protected Integer getCarouselSizeFilter() {
return carouselSizeFilter;
}
protected void setCarouselSizeFilter(Integer carouselSizeFilter) {
this.carouselSizeFilter = carouselSizeFilter;
this.filterForm.getItem(FILTER_CAROUSEL_SIZE).setValue(String.valueOf(carouselSizeFilter));
this.filterForm.getItem(FILTER_CAROUSEL_SIZE).redraw();
}
protected boolean isCarouselUsingFixedWidths() {
return carouselUsingFixedWidths;
}
protected void setCarouselUsingFixedWidths(boolean carouselUsingFixedWidths) {
this.carouselUsingFixedWidths = carouselUsingFixedWidths;
}
protected void setFilter(String name, String value) {
FormItem item = this.filterForm.getItem(name);
if (null != item) {
item.setValue(value);
}
}
protected interface CarouselAction {
/**
* Returns true if the action should be enabled based on the currently selected record(s).
*
* @param selection the currently selected record(s)
*
* @return true if the action should be enabled based on the currently selected record(s)
*/
boolean isEnabled(); //TODO add arg
/**
* Execute the action with the currently selected record(s) as the target(s).
*
* @param selection the currently selected record(s)
* @param actionValue a value optionally supplied by the action (for example, a menuItem action's selection)
*/
void executeAction(Object actionValue); //TODO add arg
}
}