/*
* RHQ Management Platform
* Copyright (C) 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.inventory.common.detail;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.google.gwt.user.client.History;
import com.smartgwt.client.types.Side;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.layout.Layout;
import org.rhq.core.domain.authz.Permission;
import org.rhq.coregui.client.BookmarkableView;
import org.rhq.coregui.client.CoreGUI;
import org.rhq.coregui.client.PermissionsLoadedListener;
import org.rhq.coregui.client.PermissionsLoader;
import org.rhq.coregui.client.RefreshableView;
import org.rhq.coregui.client.ViewChangedException;
import org.rhq.coregui.client.ViewPath;
import org.rhq.coregui.client.components.tab.SubTab;
import org.rhq.coregui.client.components.tab.TwoLevelTab;
import org.rhq.coregui.client.components.tab.TwoLevelTabSelectedEvent;
import org.rhq.coregui.client.components.tab.TwoLevelTabSelectedHandler;
import org.rhq.coregui.client.components.tab.TwoLevelTabSet;
import org.rhq.coregui.client.components.view.ViewFactory;
import org.rhq.coregui.client.inventory.common.AbstractD3GraphListView;
import org.rhq.coregui.client.util.Log;
import org.rhq.coregui.client.util.enhanced.EnhancedVLayout;
/**
* @author Greg Hinkle
* @author Ian Springer
*/
public abstract class AbstractTwoLevelTabSetView<T, U extends Layout, V extends AbstractD3GraphListView> extends
EnhancedVLayout implements
BookmarkableView, TwoLevelTabSelectedHandler {
private String baseViewPath;
private TwoLevelTabSet tabSet;
private String tabName;
private String subTabName;
private final U titleBar;
protected Set<Permission> globalPermissions;
protected V graphListView;
public AbstractTwoLevelTabSetView(String baseViewPath, U titleBar, TwoLevelTab[] tabs) {
super();
this.baseViewPath = baseViewPath;
setWidth100();
setHeight100();
this.titleBar = titleBar;
addMember(this.titleBar);
this.tabSet = new TwoLevelTabSet();
this.tabSet.setTabBarPosition(Side.TOP);
this.tabSet.setWidth100();
this.tabSet.setHeight100();
this.tabSet.setEdgeMarginSize(0);
this.tabSet.setEdgeSize(0);
this.tabSet.setTabs(tabs);
this.tabSet.addTwoLevelTabSelectedHandler(this);
addMember(this.tabSet);
}
// ------------------ Abstract Methods --------------------
public abstract Integer getSelectedItemId();
protected abstract V createD3GraphListView();
/**
*
* @param itemId
* @param viewPath
*/
protected abstract void loadSelectedItem(int itemId, ViewPath viewPath);
protected void updateTabContent(T selectedItem, boolean isRefresh) {
String currentViewPath = History.getToken();
if (!currentViewPath.startsWith(this.baseViewPath)) {
throw new ViewChangedException(this.baseViewPath + "/" + getSelectedItemId());
}
}
// ---------------------------------------------------------
protected U getTitleBar() {
return this.titleBar;
}
protected boolean updateTab(TwoLevelTab tab, boolean visible, boolean enabled) {
if (visible) {
getTabSet().setTabHidden(tab, false);
getTabSet().setTabEnabled(tab, enabled);
} else {
getTabSet().setTabHidden(tab, true);
}
return (visible && enabled);
}
protected void updateSubTab(TwoLevelTab tab, SubTab subTab, boolean visible, boolean enabled,
ViewFactory viewFactory) {
updateSubTab(tab, subTab, null, visible, enabled, viewFactory);
}
protected void updateSubTab(TwoLevelTab tab, SubTab subTab, Canvas canvas, boolean visible, boolean enabled,
ViewFactory viewFactory) {
tab.setVisible(subTab, visible);
if (visible) {
tab.setSubTabEnabled(subTab, enabled);
if (enabled) {
subTab.setCanvas(canvas);
subTab.setViewFactory(viewFactory);
}
}
}
// This is invoked by events fired in TwoLevelTabSet whenever a tab/subtab combo has been selected.
public void onTabSelected(TwoLevelTabSelectedEvent tabSelectedEvent) {
// Establishing the proper tabbed view may involve tab add/remove and async loading of content. While doing this
// we want to prevent user initiation of another tab change. To block users from clicking tabs we
// disable the tab set. We re-enable the tabset when safe. (see this method and also selectTab()).
String historyToken = tabSelectedEvent.getHistoryToken();
if (getSelectedItemId() == null) {
this.tabSet.disable();
CoreGUI.goToView(historyToken);
} else {
String tabId = tabSelectedEvent.getId();
String subTabId = tabSelectedEvent.getSubTabId();
String tabPath = "/" + tabId + "/" + subTabId;
String path = this.baseViewPath + "/" + getSelectedItemId() + tabPath;
// If the selected tab or subtab is not already the current history item, the user clicked on the tab, rather
// than going directly to the tab's URL. In this case, fire a history event to go to the tab and make it the
// current history item.
if (!(historyToken.equals(path) || historyToken.startsWith(path + "/"))) {
this.tabSet.disable();
CoreGUI.goToView(path);
} else {
// ensure the tabset is enabled if we're not going to be doing any further tab selection
this.tabSet.enable();
}
}
}
public void renderView(final ViewPath viewPath) {
new PermissionsLoader().loadExplicitGlobalPermissions(new PermissionsLoadedListener() {
@Override
public void onPermissionsLoaded(Set<Permission> permissions) {
globalPermissions = (permissions != null) ? permissions : new HashSet<Permission>();
renderTabs(viewPath);
}
});
}
private void renderTabs(final ViewPath viewPath) {
// e.g. #Resource/10010/Summary/Overview
// ^ current path
final int id = Integer.parseInt(viewPath.getCurrent().getPath());
viewPath.next();
if (!viewPath.isEnd()) {
// e.g. #Resource/10010/Summary/Overview
// ^ current path
this.tabName = viewPath.getCurrent().getPath();
viewPath.next();
if (!viewPath.isEnd()) {
// e.g. #Resource/10010/Summary/Overview
// ^ current path
this.subTabName = viewPath.getCurrent().getPath();
viewPath.next();
} else {
this.subTabName = null;
}
} else {
this.tabName = null;
}
// We are either navigating to a new detail view (new entity: resource or group) or switching tabs
// for the current entity. Changing entities may change the available tabs as the same tab set may not
// be supported by the new entity's type. To maintain a valid tab selection for the TabSet, smartgwt will
// generate events if the current tab is removed (which can happen say, when navigating from a resource of
// type A to a resource of type B). We need to ignore tab selection events generated by smartgwt because we
// already perform explicit tab management at a higher level. To do this we explicitly set events to be be
// ignored. We re-enable the event handling when safe. (see selectTab()). Similarly, even when navigating
// within the same entity, when changing tabs we need to suppress smartgwt generated tab events (when
// calling TabSet.selectTab). That event can conflict with our subtab management, navigating the user
// to the default subtab for the tab and overriding our explicit navigation to a non-default subtab.
this.tabSet.setIgnoreSelectEvents(true);
if (getSelectedItemId() == null || getSelectedItemId() != id || viewPath.isRefresh()) {
// A different entity (resource or group), load it and try to navigate to the same tabs if possible.
this.loadSelectedItem(id, viewPath);
} else {
// Same Resource - just switch tabs.
//
// until we finish the following work we're susceptible to fast-click issues in
// tree navigation. So, wait until after it's done to notify listeners that the view is
// safely rendered. Make sure to notify even on failure.
try {
this.selectTab(this.tabName, this.subTabName, viewPath);
} finally {
notifyViewRenderedListeners();
}
}
}
/**
* Select the tab/subtab with the specified titles (e.g. "Monitoring", "Graphs").
*
* @param tabName the title of the tab to select - if null, the default tab (the leftmost non-disabled one) will be selected
* @param subtabName the title of the subtab to select - if null, the default subtab (the leftmost non-disabled one) will be selected
* @param viewPath the view path, which may have additional view items to be rendered
*/
public void selectTab(String tabName, String subtabName, ViewPath viewPath) {
try {
TwoLevelTab tab = (tabName != null) ? this.tabSet.getTabByName(tabName) : null;
SubTab subtab = null;
// if the requested tab is not available for the tabset then select the default tab/subtab. Fire
// an event in order to navigate to the new path
if (tab == null || tab.getDisabled()) {
this.tabSet.setIgnoreSelectEvents(false);
subtab = selectDefaultTabAndSubTab();
return;
}
// the tab is available, now get the subtab
subtab = (subtabName != null) ? tab.getSubTabByName(subtabName) : tab.getDefaultSubTab();
// due to our attempt to perform sticky tabbing we may request an invalid subtab when
// switching resources. If the requested subtab is not available the select the default subtab
// for the tab. Fire an event in order to navigate to the new path.
if (subtab == null || tab.getLayout().isSubTabDisabled(subtab)) {
this.tabSet.setIgnoreSelectEvents(false);
subtab = selectDefaultSubTab(tab);
return;
}
// the requested tab/subtab are valid, continue with this path
// select the tab and subTab (we are suppressing event handling, so the smartgwt fired event is ignored,
// alowing us to perform the desired subtab management below)
this.tabSet.selectTab(tab);
// this call adds the subtab canvas as a member of the subtablayout
// don't show the subtab canvas until after we perform any necessary rendering.
tab.getLayout().selectSubTab(subtab, false);
// get the target canvas
Canvas subView = subtab.getCanvas();
// if this is a bookmarkable view then further rendering is deferred to its renderView method. This
// will set the basePath as well as handle any remaining view items (e.g. id of a selected item in
// a subtab that contains a Master-Details view). Otherwise, make sure we perform any required
// refresh.
if (subView instanceof BookmarkableView) {
((BookmarkableView) subView).renderView(viewPath);
} else if (subView instanceof RefreshableView && subView.isDrawn()) {
// Refresh the data on the subtab, so it's not stale.
Log.debug("Refreshing data for [" + subView.getClass().getName() + "]...");
((RefreshableView) subView).refresh();
}
subView.setVisible(true);
// ensure the tabset is enabled (disabled in onTabSelected), and redraw
this.tabSet.setIgnoreSelectEvents(false);
this.tabSet.enable();
this.tabSet.markForRedraw();
} catch (Exception e) {
this.tabSet.enable();
Log.warn("Failed to select tab " + tabName + "/" + subtabName + ".", e);
}
}
private SubTab selectDefaultTabAndSubTab() {
TwoLevelTab tab = this.tabSet.getDefaultTab();
if (tab == null) {
throw new IllegalStateException("No default tab is defined.");
}
return selectDefaultSubTab(tab);
}
private SubTab selectDefaultSubTab(TwoLevelTab tab) {
SubTab subTab = tab.getDefaultSubTab();
if (subTab == null || tab.getLayout().isSubTabDisabled(subTab)) {
CoreGUI.getErrorHandler().handleError(
MSG.view_tabs_invalidSubTab((subTab != null ? subTab.getName() : "null")));
subTab = tab.getLayout().getDefaultSubTab();
}
tab.getLayout().selectSubTab(subTab, true);
// Now that the subtab has been selected, select the tab (this will cause a tab selected event to fire).
this.tabSet.selectTab(tab);
return subTab;
}
protected abstract T getSelectedItem();
public TwoLevelTabSet getTabSet() {
return tabSet;
}
public String getTabName() {
return tabName;
}
public String getSubTabName() {
return subTabName;
}
public String getBaseViewPath() {
return baseViewPath;
}
@Override
public void destroy() {
tabSet.destroy();
super.destroy();
}
public interface ViewRenderedListener {
void onViewRendered();
}
private List<ViewRenderedListener> viewRenderedListeners = new ArrayList<ViewRenderedListener>();
public void addViewRenderedListener(ViewRenderedListener listener) {
viewRenderedListeners.add(listener);
}
protected void notifyViewRenderedListeners() {
for (ViewRenderedListener listener : viewRenderedListeners) {
listener.onViewRendered();
}
}
}