/*
* WidgetListBox.java
*
* Copyright (C) 2009-14 by RStudio, Inc.
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
package org.rstudio.core.client.widget;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.event.dom.client.HasChangeHandlers;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Widget;
// A list box that can contain arbitrary GWT widgets as options.
public class WidgetListBox<T extends Widget>
extends FocusPanel
implements HasChangeHandlers
{
private class ClickableHTMLPanel extends HTMLPanel
implements HasClickHandlers
{
public ClickableHTMLPanel()
{
super("");
}
public HandlerRegistration addClickHandler(ClickHandler handler)
{
return addDomHandler(handler, ClickEvent.getType());
}
}
public interface ListStyle extends CssResource
{
String selectedItem();
String anyItem();
String listPanel();
String outerPanel();
}
public interface Resources extends ClientBundle
{
@Source("WidgetListBox.css")
ListStyle listStyle();
}
public WidgetListBox()
{
super();
resources_ = GWT.create(Resources.class);
style_ = resources_.listStyle();
style_.ensureInjected();
panel_ = new FlowPanel();
add(panel_);
panel_.addStyleName(style_.listPanel());
addStyleName(style_.outerPanel());
}
@Override
protected void onLoad()
{
super.onLoad();
addKeyDownHandler(new KeyDownHandler()
{
@Override
public void onKeyDown(KeyDownEvent event)
{
if (event.getNativeKeyCode() == KeyCodes.KEY_DOWN &&
selectedIdx_ < (panel_.getWidgetCount() - 1))
{
setSelectedIndex(selectedIdx_+1, true);
}
else if (event.getNativeKeyCode() == KeyCodes.KEY_UP &
selectedIdx_ > 0)
{
setSelectedIndex(selectedIdx_-1, true);
}
}
});
}
@Override
public HandlerRegistration addChangeHandler(final ChangeHandler handler)
{
return handlerManager_.addHandler(ChangeEvent.getType(), handler);
}
public void addItem(T item)
{
addItem(item, true);
}
public void addItem(final T item, boolean atEnd)
{
// wrap the widget in a panel that can receive click events, indicate
// selection, etc.
final ClickableHTMLPanel panel = new ClickableHTMLPanel();
panel.addClickHandler(new ClickHandler()
{
@Override
public void onClick(ClickEvent event)
{
setSelectedIndex(panel_.getWidgetIndex(panel), true);
}
});
panel.add(item);
// add the panel to our root layout panel
if (!atEnd && panel_.getWidgetCount() > 0)
{
panel_.insert(panel, 0);
items_.add(0, item);
options_.add(0, panel);
selectedIdx_++;
}
else
{
panel_.add(panel);
items_.add(item);
options_.add(panel);
}
panel.getElement().getStyle().setPadding(itemPaddingValue_,
itemPaddingUnit_);
panel.addStyleName(style_.anyItem());
// if it's the first item, select it
if (options_.size() == 1)
setSelectedIndex(0);
else if (!atEnd && getSelectedIndex() == 1 && options_.size() > 1)
setSelectedIndex(0, true);
}
public void setSelectedIndex(int itemIdx)
{
setSelectedIndex(itemIdx, false);
}
private void setSelectedIndex(int itemIdx, boolean fireEvent)
{
String selectedStyle = resources_.listStyle().selectedItem();
panel_.getWidget(selectedIdx_).removeStyleName(selectedStyle);
selectedIdx_ = itemIdx;
panel_.getWidget(selectedIdx_).addStyleName(selectedStyle);
panel_.getWidget(selectedIdx_).getElement().scrollIntoView();
if (fireEvent)
{
DomEvent.fireNativeEvent(Document.get().createChangeEvent(),
handlerManager_);
}
}
public int getSelectedIndex()
{
return selectedIdx_;
}
public T getItemAtIdx(int idx)
{
if (idx < items_.size())
{
return items_.get(idx);
}
return null;
}
public T getSelectedItem()
{
return getItemAtIdx(getSelectedIndex());
}
public int getItemCount()
{
return items_.size();
}
public void setItemPadding(double val, Style.Unit unit)
{
itemPaddingValue_ = val;
itemPaddingUnit_ = unit;
}
private int selectedIdx_ = 0;
private FlowPanel panel_;
private List<HTMLPanel> options_ = new ArrayList<HTMLPanel>();
private List<T> items_ = new ArrayList<T>();
private Resources resources_;
private ListStyle style_;
private double itemPaddingValue_ = 5;
private Style.Unit itemPaddingUnit_ = Style.Unit.PX;
HandlerManager handlerManager_ = new HandlerManager(this);
}