/*
* RHQ Management Platform
* Copyright (C) 2005-2010 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.tab;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.History;
import com.smartgwt.client.types.Overflow;
import com.smartgwt.client.types.SelectionType;
import com.smartgwt.client.widgets.Button;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.toolbar.ToolStrip;
import org.rhq.coregui.client.CoreGUI;
import org.rhq.coregui.client.util.enhanced.EnhancedToolStrip;
import org.rhq.coregui.client.util.enhanced.EnhancedVLayout;
/**
* @author Greg Hinkle
*/
public class SubTabLayout extends EnhancedVLayout {
/** maps subTab IDs to SubTabs. Unlike names, IDs are qualified by the Tab and therefore unique. */
private Map<String, SubTab> subTabs = new LinkedHashMap<String, SubTab>();
/** IDs of subTabs that are disabled. Unlike names, IDs are qualified by the Tab and therefore unique. */
private Set<String> disabledSubTabs = new HashSet<String>();
private SubTab currentlyDisplayed;
private String currentlySelected;
private ToolStrip buttonBar;
private SubTab head;
private SubTab tail;
public SubTabLayout() {
super();
setOverflow(Overflow.AUTO);
}
@Override
protected void onInit() {
super.onInit();
setWidth100();
setHeight100();
buttonBar = new EnhancedToolStrip();
buttonBar.setWidth100();
buttonBar.setMembersMargin(30);
buttonBar.setStyleName("subtabbar");
addMember(buttonBar);
}
@Override
protected void onDraw() {
super.onDraw();
if (null == currentlySelected) {
SubTab initial = getDefaultSubTab();
if (null != initial) {
currentlySelected = initial.getId();
}
}
if (null != currentlySelected) {
selectSubTabById(currentlySelected, true);
}
}
/**
* Make subTab visible.
*
* @param subTab not null
*/
public void showSubTab(SubTab subTab) {
Button button = subTab.getButton();
if (null == button) {
button = createSubTabButton(subTab);
if (head == null && tail == null) {
head = subTab;
tail = subTab;
buttonBar.addMember(button);
} else {
SubTab successor = findClosestVisibleSuccessor(subTab);
// if successor is null then that means we are updating the tail
if (successor == null) {
tail.setVisibleNext(subTab);
subTab.setVisibleNext(null);
tail = subTab;
buttonBar.addMember(button);
} else {
SubTab previous = findClosestVisiblePredecessor(successor);
// if previous is null then that means we are updating the head
if (previous == null) {
subTab.setVisibleNext(head);
head = subTab;
buttonBar.addMember(button, 0);
} else {
subTab.setVisibleNext(previous.getVisibleNext());
previous.setVisibleNext(subTab);
buttonBar.addMember(button, buttonBar.getMemberNumber(previous.getButton().getID()) + 1);
}
}
}
subTab.setButton(button);
}
button.show();
}
/**
* Make subTab not visible. Keeps any associated Canvas.
*
* @param subTab not null
*/
public void hideSubTab(SubTab subTab) {
SubTab previous = findClosestVisiblePredecessor(subTab);
if (previous == null) {
head = subTab.getVisibleNext();
} else {
previous.setVisibleNext(subTab.getVisibleNext());
// check to see if the tail needs to be updated. If the
// following check is true, then that means visiblePrevious is
// now the tail.
if (previous.getVisibleNext() == null) {
tail = previous;
}
}
subTab.setVisibleNext(null);
subTab.destroyButton();
}
public boolean isSubTabVisible(SubTab subTab) {
return (null != subTab && null != subTab.getButton());
}
private Button createSubTabButton(final SubTab subTab) {
Button button = new Button(subTab.getTitle());
button.setShowRollOver(false);
button.setActionType(SelectionType.RADIO);
button.setRadioGroup("subTabs");
button.setBorder(null);
button.setAutoFit(true);
button.setBaseStyle("SubTabButton");
// button.setStyleName("SubTabButton");
// button.setStylePrimaryName("SubTabButton");
button.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent clickEvent) {
SubTabLayout.this.currentlySelected = subTab.getId();
fireSubTabSelection();
markForRedraw();
}
});
return (button);
}
/**
* Ignored if not visible. Otherwise, enabled.
*/
public void enableSubTab(SubTab subTab) {
if (isSubTabVisible(subTab)) {
disabledSubTabs.remove(subTab.getId());
subTab.getButton().enable();
subTab.getButton().show();
}
}
/**
* Ignored if not visible. Otherwise, disabled.
*/
public void disableSubTab(SubTab subTab) {
if (isSubTabVisible(subTab)) {
disabledSubTabs.add(subTab.getId());
subTab.getButton().disable();
subTab.getButton().show();
}
}
/**
* @return true if not visible or disabled
*/
public boolean isSubTabDisabled(SubTab subTab) {
return (!isSubTabVisible(subTab) || subTab.getButton().getDisabled());
}
public void registerSubTabs(SubTab... tabs) {
for (SubTab subTab : tabs) {
String id = subTab.getId();
subTabs.put(id, subTab);
}
buildSubTabList(tabs);
}
private void buildSubTabList(SubTab[] tabs) {
// head = tabs[0];
// tail = tabs[tabs.length - 1];
SubTab current = tabs[0];
for (int i = 1; i < tabs.length; ++i) {
current.setActualNext(tabs[i]);
//current.setVisibleNext(tabs[i]);
current = tabs[i];
}
}
/**
* Walks the list of visible tabs to find the closest, visible predecessor. Because it
* is assumed that the tab argument is visible, one of the following must hold:
* <ul>
* <li>The tab is the head so it has no predecessor or</li>
* <li>head is null which means the list has not yet been initialized or</li>
* <li>A predecessor is reached which may be the tail</li>
* </ul>
*
* @param tab A {@link SubTab tab} that is currently visible
* @return The closest, visible predecessor or null if the tab is the head or if the
* list is not fully initialized. i.e., head and tail are null
*/
private SubTab findClosestVisiblePredecessor(SubTab tab) {
// head could be null if the list is not yet initialized. The head and
// tail are null until a node is added to the list.
if (tab == head || head == null) {
return null;
}
SubTab current = head;
while (current != tab) {
// if we have reached the visible tail or the immediate predecessor
// of the tab, then return it.
if (current.getVisibleNext() == null || current.getVisibleNext() == tab) {
return current;
}
current = current.getVisibleNext();
}
// Not sure that we should ever reach the following statement.
return null;
}
/**
* Walks the list to find the closest, visible successor. It is assumed that the tab
* argument is not visible.
*
* @param tab A {@link SubTab tab} that is currently hidden
* @return The closest, visisble successor or null if the insertion point
* is the tail.
*/
private SubTab findClosestVisibleSuccessor(SubTab tab) {
SubTab current = tab;
while (current != null) {
// Walk the list of tabs until we reach a visible successor or the tail
if (current.getVisibleNext() == null && current != tail) {
current = current.getActualNext();
} else {
return current;
}
}
// if we reach this point then that means we will be inserting at the tail
return null;
}
public SubTab getDefaultSubTab() {
// the default subTab is the first one in the set that is visible and not disabled
for (SubTab subTab : this.subTabs.values()) {
if (!isSubTabDisabled(subTab)) {
return subTab;
}
}
return null;
}
/**
* @param subtab the subtab to select
* @param showCanvas if true then ensure the subtab canvas is shown. Otherwise the state is unchanged.
* @return true if selected successfully, otherwise false
*/
public boolean selectSubTab(SubTab subTab, boolean showCanvas) {
if (subTab == null) {
throw new IllegalArgumentException("subTab is null.");
}
return selectSubTabById(subTab.getId(), showCanvas);
}
/**
* @param Id the subtab Id
* @param showCanvas if true then ensure the subtab canvas is shown. Otherwise the state is unchanged.
* @return true if selected successfully, otherwise false
*/
public boolean selectSubTabById(String id, boolean showCanvas) {
boolean foundTab = false;
for (String subTabId : this.subTabs.keySet()) {
if (subTabId.equals(id)) {
if (this.disabledSubTabs.contains(subTabId)) {
// Nice try - user tried to select a disabled tab, probably by going directly to a bookmark URL.
SubTab subTab = this.subTabs.get(subTabId);
CoreGUI.getErrorHandler().handleError(MSG.view_subTab_error_disabled(subTab.getTitle()));
} else {
this.currentlySelected = subTabId;
foundTab = true;
}
break;
}
}
if (foundTab) {
setCurrentlySelected(showCanvas);
}
return foundTab;
}
public SubTab getSubTabByName(String name) {
for (String subTabId : this.subTabs.keySet()) {
SubTab subTab = this.subTabs.get(subTabId);
if (subTab.getName().equals(name)) {
return subTab;
}
}
return null;
}
public SubTab getSubTabById(String id) {
for (String subTabId : this.subTabs.keySet()) {
if (subTabId.equals(id)) {
return this.subTabs.get(subTabId);
}
}
return null;
}
/**
* @param name subtab name
* @param showCanvas if true then ensure the subtab canvas is shown. Otherwise the state is unchanged.
* @return true if selected successfully, otherwise false
*/
public boolean selectSubTabByName(String name, boolean showCanvas) {
SubTab subTab = getSubTabByName(name);
if (subTab == null) {
return false;
} else {
if (this.disabledSubTabs.contains(subTab.getId())) {
// Nice try - user tried to select a disabled tab, probably by going directly to a bookmark URL.
CoreGUI.getErrorHandler().handleError(MSG.view_subTab_error_disabled(subTab.getTitle()));
return false;
}
this.currentlySelected = subTab.getId();
setCurrentlySelected(showCanvas);
return true;
}
}
public SubTab getCurrentSubTab() {
if (null == currentlySelected) {
SubTab current = getDefaultSubTab();
if (null != current) {
currentlySelected = current.getId();
}
return current;
}
return this.subTabs.get(this.currentlySelected);
}
// ------- Event support -------
// Done with a separate handler manager from parent class on purpose (compatibility issue)
private HandlerManager hm = new HandlerManager(this);
public HandlerRegistration addTwoLevelTabSelectedHandler(TwoLevelTabSelectedHandler handler) {
return hm.addHandler(TwoLevelTabSelectedEvent.TYPE, handler);
}
public void fireSubTabSelection() {
TwoLevelTabSelectedEvent event = new TwoLevelTabSelectedEvent("?", getCurrentSubTab().getName(), -1,
getCurrentCanvas(), History.getToken());
hm.fireEvent(event);
}
public Canvas getCurrentCanvas() {
return currentlyDisplayed != null ? currentlyDisplayed.getCanvas() : subTabs.get(currentlySelected).getCanvas();
}
/**
* Destroy all the currently held views so that they can be replaced with new versions
*/
void destroyViews() {
for (SubTab subTab : subTabs.values()) {
subTab.destroyCanvas();
}
this.currentlyDisplayed = null;
}
private void setCurrentlySelected(boolean showCanvas) {
if (null != this.currentlySelected) {
Button button = this.subTabs.get(this.currentlySelected).getButton();
button.select();
SubTab currentSubTab = this.subTabs.get(this.currentlySelected);
if (this.currentlyDisplayed != null && this.currentlyDisplayed.getCanvas() != currentSubTab.getCanvas()) {
try {
this.currentlyDisplayed.getCanvas().hide();
} catch (Exception e) {
// ignore this
}
}
Canvas canvas = currentSubTab.getCanvas();
if (canvas != null) {
if (hasMember(canvas)) {
if (!canvas.isVisible() && showCanvas) {
canvas.show();
}
} else {
if (!canvas.isCreated()) {
canvas.setOverflow(Overflow.SCROLL);
}
addMember(canvas);
if (!canvas.isVisible() && showCanvas) {
canvas.show();
}
}
markForRedraw();
this.currentlyDisplayed = currentSubTab;
}
}
}
}