/*
* 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.LinkedHashMap;
import java.util.Map;
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.widgets.tab.Tab;
import com.smartgwt.client.widgets.tab.events.TabSelectedEvent;
import com.smartgwt.client.widgets.tab.events.TabSelectedHandler;
/**
* A tab set where each {@link TwoLevelTab tab} has one or more {@link SubTab subtab}s.
*
* @author Jay Shaughnessy
* @author John Sanda
*/
public class TwoLevelTabSet extends NamedTabSet implements TabSelectedHandler, TwoLevelTabSelectedHandler {
private Map<String, TwoLevelTab> hiddenTabs = new LinkedHashMap<String, TwoLevelTab>();
private boolean ignoreSelectEvents = false;
private TwoLevelTab head;
/**
* This is the visible tail. Because the actual order of tabs is fixed we know that the
* actual tail will always be the content tab.
*/
private TwoLevelTab tail;
public TwoLevelTabSet() {
super();
// Need to set destroyPanes property to false so that we do not lose tab
// content when hiding a tab.
setDestroyPanes(false);
}
public void setTabs(TwoLevelTab... tabs) {
super.setTabs(tabs);
for (TwoLevelTab tab : tabs) {
tab.getLayout().addTwoLevelTabSelectedHandler(this);
updateTab(tab, tab.getPane());
}
buildTabList();
addTabSelectedHandler(this);
}
/**
* This method initializes the head and tail pointers. Then it initializes the
* {@link TwoLevelTab#getActualNext actualNext} and {@link TwoLevelTab#getVisibleNext visibleNext}
* properties of each tab. This list is built so that when hiding and showing tabs, the
* tab order remains consistent. The order of the list is the same as the order of the
* tabs passed to {@link #setTabs(TwoLevelTab...)}
*/
private void buildTabList() {
TwoLevelTab[] tabs = getTabs();
head = tabs[0];
tail = tabs[tabs.length - 1];
TwoLevelTab current = head;
for (int i = 1; i < tabs.length; ++i) {
current.setActualNext(tabs[i]);
current.setVisibleNext(tabs[i]);
current = tabs[i];
}
}
public TwoLevelTab[] getTabs() {
Tab[] tabs = super.getTabs();
TwoLevelTab[] twoLevelTabs = new TwoLevelTab[tabs.length];
for (int i = 0, tabsLength = tabs.length; i < tabsLength; i++) {
Tab tab = tabs[i];
if (!(tab instanceof TwoLevelTab)) {
throw new IllegalStateException("TwoLevelTabSet contains a Tab that is not a TwoLevelTab.");
}
twoLevelTabs[i] = (TwoLevelTab) tab;
}
return twoLevelTabs;
}
// Smartgwt does not currently offer the ability to hide a Tab (why!) so we fake it here. This allows us to keep
// the Tab structure in place while removing it from the TabSet
public void setTabHidden(TwoLevelTab tab, boolean hidden) {
if (hidden) {
if (hiddenTabs.containsKey(tab.getName())) {
return;
}
TwoLevelTab visiblePrevious = findClosestVisiblePredecessor(tab);
if (visiblePrevious == null) {
// if visiblePrevious is null then that means we are updating
// then head. Note that as of now (02/21/2012), the visible head,
// the summary tab, is fixed, so we don't really need to worry
// about updating the head; however, doing so will make it easier
// to support things like hiding arbitrary tabs or reordering tabs.
head = tab.getVisibleNext();
} else {
visiblePrevious.setVisibleNext(tab.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 (visiblePrevious.getVisibleNext() == null) {
tail = visiblePrevious;
}
}
tab.setVisibleNext(null);
// Note that removing the tab does *not* destroy its content pane
// since we set the destroyPanes property to false in the
removeTab(tab);
hiddenTabs.put(tab.getName(), tab);
} else {
if (!hiddenTabs.containsKey(tab.getName())) {
return;
}
hiddenTabs.remove(tab.getName());
TwoLevelTab successor = findClosestVisibleSuccessor(tab);
if (successor == null) {
// if successor is null then that means we are updating the tail
tail.setVisibleNext(tab);
tail = tab;
addTab(tab);
} else {
TwoLevelTab visiblePrevious = findClosestVisiblePredecessor(successor);
tab.setVisibleNext(visiblePrevious.getVisibleNext());
visiblePrevious.setVisibleNext(tab);
addTab(tab, (getTabNumber(visiblePrevious.getID()) + 1));
}
}
}
/**
* Walks the list of tabs to find the closest, visible predecessor.
*
* @param tab A {@link TwoLevelTab tab} that is currently visible
* @return The closest, visible predecessor or null if have the head
*/
private TwoLevelTab findClosestVisiblePredecessor(TwoLevelTab tab) {
if (tab == head) {
return null;
}
TwoLevelTab 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 what we should do if we get here. return null for now
return null;
}
/**
* Walks the list to find the closest, visible successor.
*
* @param tab A {@link TwoLevelTab tab} that is currently hidden
* @return The closest, visisble successor or null if the insertion point
* is the tail.
*/
private TwoLevelTab findClosestVisibleSuccessor(TwoLevelTab tab) {
TwoLevelTab 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 void destroyViews() {
for (TwoLevelTab tab : getTabs()) {
tab.getLayout().destroyViews();
}
}
// ------- Event support -------
// Done with a separate handler manager from parent class on purpose (compatibility issue)
private HandlerManager m = new HandlerManager(this);
public HandlerRegistration addTwoLevelTabSelectedHandler(TwoLevelTabSelectedHandler handler) {
return m.addHandler(TwoLevelTabSelectedEvent.TYPE, handler);
}
// This is invoked by smartgwt when the user clicks on a Tab in the TabSet, or TabSet.selectTab() is called. It
// sets the current SubTab and fires an event to notify AbstractTwoLevelTabSet that a tab/subtab has been selected.
public void onTabSelected(TabSelectedEvent tabSelectedEvent) {
// if requested, ignore select tab notifications. smartgwt can generate unwanted notifications
// while we manipulate the tabset (e.g. when hiding the current tab). We want to manage this at a higher level
if (isIgnoreSelectEvents()) {
return;
}
TwoLevelTab tab = (TwoLevelTab) getSelectedTab();
SubTab currentSubTab = tab.getLayout().getCurrentSubTab();
if (null != currentSubTab) {
TwoLevelTabSelectedEvent event = new TwoLevelTabSelectedEvent(tab.getName(), tab.getLayout()
.getCurrentSubTab().getName(), tabSelectedEvent.getTabNum(), tab.getLayout().getCurrentCanvas(),
History.getToken());
m.fireEvent(event);
}
}
// This is invoked by an event fired in SubTabLayout when the user clicks a SubTab button. It sets the Tab
// and fires an event to notify AbstractTwoLevelTabSet that a tab/subtab has been selected.
public void onTabSelected(TwoLevelTabSelectedEvent tabSelectedEvent) {
// if requested, ignore select tab notifications. smartgwt can generate unwanted notifications
// while we manipulate the tabset (e.g. when hiding the current tab). We want to manage this at a higher level
if (isIgnoreSelectEvents()) {
return;
}
tabSelectedEvent.setTabNum(getSelectedTabNumber());
Tab tab = getSelectedTab();
tabSelectedEvent.setId(this.getTabByTitle(tab.getTitle()).getName());
m.fireEvent(tabSelectedEvent);
}
public TwoLevelTab getDefaultTab() {
TwoLevelTab[] tabs = getTabs();
for (TwoLevelTab tab : tabs) {
if (!tab.getDisabled()) {
return tab;
}
}
return null;
}
public TwoLevelTab getTabByName(String name) {
return (TwoLevelTab) super.getTabByName(name);
}
public TwoLevelTab getTabByTitle(String title) {
return (TwoLevelTab) super.getTabByTitle(title);
}
public void setTabEnabled(TwoLevelTab tab, boolean enabled) {
if (enabled) {
enableTab(tab);
} else {
disableTab(tab);
}
}
public boolean isIgnoreSelectEvents() {
return ignoreSelectEvents;
}
public void setIgnoreSelectEvents(boolean ignoreSelectEvents) {
this.ignoreSelectEvents = ignoreSelectEvents;
}
@Override
public void destroy() {
// add the hidden tabs back under the TabSet. This will get them destroyed by smartgwt when the tabset
// goes away. There is no explicit Tab.destroy().
for (TwoLevelTab tab : hiddenTabs.values()) {
addTab(tab);
}
for (TwoLevelTab tab : getTabs()) {
tab.getLayout().destroyViews();
}
super.destroy();
}
}