/*
* Copyright 2010 bufferings[at]gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package bufferings.ktr.wjr.client.ui.widget;
import static bufferings.ktr.wjr.client.ui.widget.JQueryUI.*;
import static bufferings.ktr.wjr.shared.util.Preconditions.*;
import java.util.ArrayList;
import java.util.Iterator;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.logical.shared.BeforeSelectionEvent;
import com.google.gwt.event.logical.shared.BeforeSelectionHandler;
import com.google.gwt.event.logical.shared.HasBeforeSelectionHandlers;
import com.google.gwt.event.logical.shared.HasSelectionHandlers;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.IndexedPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.ProvidesResize;
import com.google.gwt.user.client.ui.ResizeComposite;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.WidgetCollection;
/**
* The tab panel with JQueryUI theme.
*
* @author bufferings[at]gmail.com
*/
public class WjrTabPanel extends ResizeComposite implements ProvidesResize,
IndexedPanel, HasBeforeSelectionHandlers<Integer>,
HasSelectionHandlers<Integer> {
private static WjrTabPanelUiBinder uiBinder =
GWT.create(WjrTabPanelUiBinder.class);
interface WjrTabPanelUiBinder extends UiBinder<Widget, WjrTabPanel> {
}
/**
* CssResource used in the WjrTabPanel.
*
* @author bufferings[at]gmail.com
*/
protected interface MyStyle extends CssResource {
/**
* The tab selected style.
*
* @return The tab selected style.
*/
String tabSelected();
/**
* The tab unselected style.
*
* @return The tab unselected style.
*/
String tabUnselected();
}
protected static final int PADDING = 3;
protected static final double BAR_HEIGHT = 35;
/**
* The css style.
*/
@UiField
protected MyStyle style;
/**
* The layout panel to control tabs and contents.
*/
@UiField
protected LayoutPanel panel;
/**
* The tab bar panel.
*/
@UiField
protected HorizontalPanel tabBar;
/**
* The tab widgets
*/
protected ArrayList<Tab> tabs;
/**
* The content widgets.
*/
protected WidgetCollection children;
/**
* The tab selected index.
*/
protected int selectedIndex = -1;
/**
* Constructs the WjrTabPanel.
*/
public WjrTabPanel() {
initWidget(uiBinder.createAndBindUi(this));
tabs = new ArrayList<Tab>();
children = new WidgetCollection(panel);
}
/**
* {@inheritDoc}
*/
public HandlerRegistration addBeforeSelectionHandler(
BeforeSelectionHandler<Integer> handler) {
return addHandler(handler, BeforeSelectionEvent.getType());
}
/**
* {@inheritDoc}
*/
public HandlerRegistration addSelectionHandler(
SelectionHandler<Integer> handler) {
return addHandler(handler, SelectionEvent.getType());
}
/**
* Adds the tab item.
*
* @param child
* The child tab contents widget.
* @param text
* The tab text.
* @param tabStopIndex
* The tab index.
*/
public void add(Widget child, String text, int tabStopIndex) {
insert(child, text, getWidgetCount(), tabStopIndex);
}
/**
* Inserts the tab item.
*
* @param child
* The child tab contents widget.
* @param text
* The tab text.
* @param beforeIndex
* The index to insert into.
* @param tabStopIndex
* The tab index.
*/
public void insert(final Widget child, String text, int beforeIndex,
int tabStopIndex) {
checkArgument(
(beforeIndex >= 0) && (beforeIndex <= getWidgetCount()),
"beforeIndex out of bounds");
int idx = getWidgetIndex(child);
if (idx != -1) {
remove(child);
if (idx < beforeIndex) {
beforeIndex--;
}
}
final Tab tab = new Tab(text, tabStopIndex);
children.insert(child, beforeIndex);
tabs.add(beforeIndex, tab);
tabBar.insert(tab, beforeIndex);
tab.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
selectTab(child);
}
});
panel.insert(child, beforeIndex);
layoutChild(child);
if (selectedIndex == -1) {
selectTab(0);
}
}
/**
* Clears all the tabs and the contents.
*/
public void clear() {
Iterator<Widget> it = iterator();
while (it.hasNext()) {
it.next();
it.remove();
}
}
/**
* Gets the selected index.
*
* @return The selected index.
*/
public int getSelectedIndex() {
return selectedIndex;
}
/**
* {@inheritDoc}
*/
public Widget getWidget(int index) {
checkIndex(index);
return children.get(index);
}
/**
* {@inheritDoc}
*/
public int getWidgetCount() {
return children.size();
}
/**
* {@inheritDoc}
*/
public int getWidgetIndex(Widget child) {
return children.indexOf(child);
}
/**
* Gets the iterator of children widgets.
*
* @return The iterator of children widgets.
*/
public Iterator<Widget> iterator() {
return children.iterator();
}
/**
* {@inheritDoc}
*/
public boolean remove(int index) {
if ((index < 0) || (index >= getWidgetCount())) {
return false;
}
Widget child = children.get(index);
tabBar.remove(index);
panel.remove(child);
children.remove(index);
tabs.remove(index);
if (index == selectedIndex) {
selectedIndex = -1;
if (getWidgetCount() > 0) {
selectTab(0);
}
} else if (index < selectedIndex) {
--selectedIndex;
}
return true;
}
/**
* Removes the widget.
*
* If the widget is not a child of this composite, then return false.
*
* @param w
* The widget to remove.
* @return False if the widget is not present, true if present.
*/
public boolean remove(Widget w) {
int index = children.indexOf(w);
if (index == -1) {
return false;
}
return remove(index);
}
/**
* Selects the tab and show its contents.
*
* @param index
* The index of the tab to show.
*/
public void selectTab(int index) {
checkIndex(index);
if (index == selectedIndex) {
return;
}
BeforeSelectionEvent<Integer> event =
BeforeSelectionEvent.fire(this, index);
if ((event != null) && event.isCanceled()) {
return;
}
if (selectedIndex != -1) {
Widget child = children.get(selectedIndex);
Element container = panel.getWidgetContainerElement(child);
container.getStyle().setDisplay(Display.NONE);
child.setVisible(false);
tabs.get(selectedIndex).setSelected(false);
}
Widget child = children.get(index);
Element container = panel.getWidgetContainerElement(child);
container.getStyle().clearDisplay();
child.setVisible(true);
tabs.get(index).setSelected(true);
selectedIndex = index;
SelectionEvent.fire(this, index);
}
/**
* Selects the tab from the contents.
*
* @param child
* The contents to show.
*/
public void selectTab(Widget child) {
selectTab(getWidgetIndex(child));
}
/**
* Checks the index is valid.
*
* @param index
* The index to check.
* @throws IllegalArgumentException
* if the index is out of bounds.
*/
protected void checkIndex(int index) {
checkArgument(
(index >= 0) && (index < getWidgetCount()),
"Index out of bounds");
}
/**
* Layouts the content widget.
*
* @param child
* The content widget.
*/
protected void layoutChild(Widget child) {
panel.setWidgetLeftRight(child, PADDING, Unit.PX, PADDING, Unit.PX);
panel.setWidgetTopBottom(child, BAR_HEIGHT + PADDING, Unit.PX, 0, Unit.PX);
panel.getWidgetContainerElement(child).getStyle().setDisplay(Display.NONE);
child.setVisible(false);
}
/**
* The label for tab with JQueryUI style.
*
* @author bufferings[at]gmail.com
*/
protected class Tab extends Composite {
/**
* For controling key events.
*/
protected boolean lastWasKeyDown;
/**
* Constructs the Tab
*
* @param text
* the text to show.
* @param tabStopIndex
* The tab index.
*/
public Tab(String text, int tabStopIndex) {
super();
Label label = new Label(text);
label.setStyleName("");
FocusPanel focusPanel = new FocusPanel();
focusPanel.add(label);
focusPanel.setTabIndex(tabStopIndex);
initWidget(focusPanel);
setStyleName(join(UI_WIDGET, UI_STATE_DEFAULT, UI_CORNER_TOP));
setSelected(false);
sinkEvents(Event.ONMOUSEOVER | Event.ONMOUSEOUT | Event.KEYEVENTS);
}
/**
* {@inheritDoc}
*/
public void onBrowserEvent(Event event) {
int eventType = DOM.eventGetType(event);
switch (eventType) {
case Event.ONMOUSEOVER:
addStyleName(UI_STATE_HOVER);
break;
case Event.ONMOUSEOUT:
removeStyleName(UI_STATE_HOVER);
break;
}
switch (eventType) {
case Event.ONKEYDOWN:
case Event.ONKEYPRESS:
case Event.ONKEYUP:
if (DOM.eventGetAltKey(event) || DOM.eventGetMetaKey(event)) {
super.onBrowserEvent(event);
return;
}
}
switch (eventType) {
case Event.ONKEYDOWN: {
keyboardNavigation(event);
lastWasKeyDown = true;
break;
}
case Event.ONKEYPRESS: {
if (!lastWasKeyDown) {
keyboardNavigation(event);
}
lastWasKeyDown = false;
break;
}
case Event.ONKEYUP: {
lastWasKeyDown = false;
break;
}
}
switch (eventType) {
case Event.ONKEYDOWN:
case Event.ONKEYUP: {
if (isKeyAssigned(DOM.eventGetKeyCode(event))) {
DOM.eventCancelBubble(event, true);
DOM.eventPreventDefault(event);
return;
}
}
}
super.onBrowserEvent(event);
}
/**
* {@inheritDoc}
*/
public HandlerRegistration addClickHandler(ClickHandler handler) {
return addDomHandler(handler, ClickEvent.getType());
}
/**
* Sets the selected style.
*
* @param selected
* The selected value.
*/
public void setSelected(boolean selected) {
if (selected) {
addStyleName(UI_STATE_ACTIVE);
addStyleName(style.tabSelected());
removeStyleName(style.tabUnselected());
} else {
removeStyleName(UI_STATE_ACTIVE);
removeStyleName(style.tabSelected());
addStyleName(style.tabUnselected());
}
}
/**
* Check if the key code is assigned or not.
*
* @param code
* The key code.
* @return True if the key is assigned, false if not.
*/
protected boolean isKeyAssigned(int code) {
switch (code) {
case KeyCodes.KEY_ENTER:
return true;
default:
return false;
}
}
private void keyboardNavigation(Event event) {
int code = DOM.eventGetKeyCode(event);
switch (code) {
case KeyCodes.KEY_ENTER: {
NativeEvent nativeEvent =
Document.get().createClickEvent(
0,
0,
0,
0,
0,
false,
false,
false,
false);
ClickEvent.fireNativeEvent(nativeEvent, this);
break;
}
}
}
}
}