package DisplayProject.controls;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import org.apache.log4j.Logger;
import DisplayProject.Array_Of_Panel;
import DisplayProject.CompoundField;
import DisplayProject.Constants;
import DisplayProject.GridField;
import DisplayProject.LayoutManagerHelper;
import DisplayProject.TabInfo;
import DisplayProject.actions.Caption;
import DisplayProject.actions.FrameWeight;
import DisplayProject.actions.WidgetState;
import DisplayProject.events.ClientEventManager;
import DisplayProject.plaf.TabFolderUI;
import Framework.EventManager;
import Framework.ForteKeyboardFocusManager;
import Framework.ParameterHolder;
import Framework.TextData;
/**
* The TabFolder class defines a tab folder, a compound widget that displays an array of panels as tabbed pages.
* @since 22/3/08
*
*/
@SuppressWarnings("serial")
public class TabFolder extends JTabbedPane implements FocusListener, CompoundField{
private static Logger _log = Logger.getLogger(TabFolder.class);
protected int previousTabIndex = -1;
protected volatile boolean ignoreChange = false;
private static final String uiClassID = "TabFolderUI";
/**
* if set to true the tab folder will not change to a new tab either by
* user action or by programatic change
*/
protected volatile boolean preventChange = false;
public TabFolder() {
super();
this.setupControl();
}
public TabFolder(int tabPlacement, int tabLayoutPolicy) {
super(tabPlacement, tabLayoutPolicy);
this.setupControl();
}
public TabFolder(int tabPlacement) {
super(tabPlacement);
this.setupControl();
}
private void setupControl() {
this.putClientProperty("qq_hidden_pages", new Hashtable<Object, Object>());
this.addFocusListener(this); // CraigM:21/08/2008 - Moved from unnecessary TabbedPaneModel
// TF:15/12/2009:DET-141:Set this up to allow inheriting of popup menu
this.setInheritsPopupMenu(true);
}
@Override
public void doLayout() {
// This overload is to ensure that tab folder resize to the minimum
// size when the size policy is set to natural
// TF:30/11/07:Added the layout debug stuff here
LayoutManagerHelper.setDebugBackgroundColor(this);
LayoutManagerHelper.makeDebugTooltip(this);
// TF:13/8/07: A tab folder should always be big enough to
// contain all it's children, irrespective of size policy
// Now, the tab folder layout manager built into Swing computes
// the size needed to fully house the Panels on all
// the tabs, we need only to reset the minimum size to enforce
// the correct size
Dimension d = getSize();
setMinimumSize(null);
Dimension minSize = getMinimumSize();
int newWidth = this.getWidth();
int newHeight = this.getHeight();
int polW = GridField.getConstraints(this).getWidthPolicy();
int polH = GridField.getConstraints(this).getHeightPolicy();
/*
* get the border sizes
*/
Insets pad = this.getInsets();
/*
* calculate new width as needed
*/
int desiredWidth = pad.left + minSize.width + pad.right;
if (desiredWidth > newWidth || polW == Constants.SP_NATURAL) {
newWidth = desiredWidth;
}
// If we've got an explicit size set, maintain that as the lower
// bound
// TF: If we have tab folder in forte which has an explicit size
// set, but this is
// too small to contain it's children, Forte would expand this
// up. We currently just
// always obey the minimum size. Logged as JIRA ITC-5
if (polW == Constants.SP_EXPLICIT) {
// if (newWidth < d.width) {
newWidth = d.width;
// }
}
/*
* calculate new height as needed
*/
int desiredHeight = pad.top + minSize.height + pad.bottom;
if (desiredHeight > newHeight || polH == Constants.SP_NATURAL) {
newHeight = desiredHeight;
}
// If we've got an explicit size set, maintain that as the lower
// bound
// TF: If we have tab folder in forte which has an explicit size
// set, but this is
// too small to contain it's children, Forte would expand this
// up. We currently just
// always obey the minimum size.
if (polH == Constants.SP_EXPLICIT) {
// if (newHeight < d.height + pad.top + pad.bottom) {
// newHeight = d.height + pad.top + pad.bottom;
// }
newHeight = d.height;
}
Dimension newDim = new Dimension(newWidth, newHeight);
if (!(newDim.equals(this.getSize()))) {
this.setMinimumSize(newDim);
this.setPreferredSize(newDim);
this.setBounds(this.getX(), this.getY(), newWidth,
newHeight);
getParent().invalidate();
getParent().validate();
}
super.doLayout();
}
//PM:24/4/08 removed to help debugging
// @Override
// public String toString() {
// return "TabFolder [" + this.getName() + "]";
// }
//PM:25/4/08 not sure this is neaded anymore
// @Override
// public void addTab(String title, Component component) {
// /*******************************************************************
// * TF:15/8/07:Panels displayed in tabs hide the bottom 2 pixels
// * (highlighting) of the tab. Hence, to work around this we'll
// * render the tab object as non-opaque This has a side effect of not
// * allowing pages of a tab folder to have their own background
// * colour.
// ******************************************************************/
// if (component instanceof JComponent) {
// ((JComponent) component).setOpaque(false);
// }
// super.addTab(title, component);
// }
//
// @Override
// public void addTab(String title, Icon icon, Component component) {
// if (component instanceof JComponent) {
// ((JComponent) component).setOpaque(false);
// }
// super.addTab(title, icon, component);
// }
//
// @Override
// public void addTab(String title, Icon icon, Component component,
// String tip) {
// if (component instanceof JComponent) {
// ((JComponent) component).setOpaque(false);
// }
// super.addTab(title, icon, component, tip);
// }
@Override
public Dimension getMinimumSize() {
if (!isMinimumSizeSet()) {
Dimension d = super.getMinimumSize();
int width = this.getWidth();
int height = this.getHeight();
int polW = GridField.getConstraints(this).getWidthPolicy();
int polH = GridField.getConstraints(this).getHeightPolicy();
/*
* get the border sizes
*/
Insets pad = this.getInsets();
// If we've got an explicit size set, maintain that as the
// lower bound
if (polH == Constants.SP_EXPLICIT
&& pad.top + pad.bottom + height > d.height) {
d.height = height + pad.top + pad.bottom;
}
// If we've got an explicit size set, maintain that as the
// lower bound
if (polW == Constants.SP_EXPLICIT
&& pad.left + pad.right + width > d.width) {
d.width = width + pad.left + pad.right;
}
return d;
} else {
return super.getMinimumSize();
}
}
@Override
public void remove(Component component) {
// PM:29/11/07 remove all tabs
List<JPanel> pages = getAllPages();
pages.remove(component);
super.remove(component);
}
private static final String PARENT_TAB_FOLDER_PROPERTY = "qq_Tab_Parent";
private static final String IS_TAB_VISIBLE_PROPERTY = "qq_Tab_visible";
private static final String TAB_INFO_PROPERTY = "qq_tabInfo";
private static final String ALL_PAGES_PROPERTY = "qq_all_pages";
@Override
public void insertTab(String title, Icon icon, Component component,
String tip, int index) {
((JPanel)component).putClientProperty(PARENT_TAB_FOLDER_PROPERTY, this);
// PM:29/11/07 store all tabs
if (WidgetState.get((JPanel)component) != Constants.FS_INVISIBLE) {//PM:14/01/2009: Only add the page if it is visible
((JPanel)component).putClientProperty(IS_TAB_VISIBLE_PROPERTY, true);
super.insertTab(title, icon, component, tip, getTabCount());
} else {//PM:14/01/2009:Otherwise add the page to the hidden pages list
((JPanel)component).putClientProperty(IS_TAB_VISIBLE_PROPERTY, false);
TabInfo ti = (TabInfo)((JComponent)component).getClientProperty(TAB_INFO_PROPERTY);
if (ti == null) {
ti = new TabInfo(title, null, index, (JPanel)component);
}
getHiddenPages().put(component, ti);
}
List<JPanel> pages = getAllPages();
if (!pages.contains(component)) //PM:22/07/2008:but only if there are not already stored
pages.add(index, (JPanel) component);
}
@SuppressWarnings("unchecked")
public List<JPanel> getAllPages() {
// PM:29/11/07 store all tabs
List<JPanel> pages = (List<JPanel>) getClientProperty(ALL_PAGES_PROPERTY);
if (pages == null) {
pages = new ArrayList<JPanel>();
putClientProperty(ALL_PAGES_PROPERTY, pages);
}
return pages;
}
@SuppressWarnings("unchecked")
//PM:22/07/2008:AXA
public Map<Component, TabInfo> getHiddenPages(){
return (Map<Component, TabInfo>)getClientProperty("qq_hidden_pages");
}
/**
* This flag is used to determine if a tab change was effected from the user clicking the mouse on the tab
*/
private boolean isSetViaMouse = false;
@Override
public void setSelectedIndex(final int index) {
/**
* PM:22/3/08
* This is an odd requirement for customers wanting
* to verify a tab pane before it is changed.
* So we post and wait
*/
EventManager.startEventChain();
Hashtable<String, ParameterHolder> params = new Hashtable<String, ParameterHolder>();
EventManager.postEventAndWait(this, "BeforeTabChange", params);
// System.out.println("BeforeTabChange");
List<JPanel> allPages = this.getAllPages();
int selectedIndex = getSelectedIndex();
if (selectedIndex > -1){
JPanel page = (JPanel)getComponentAt(selectedIndex);
setPreviousTabIndex(allPages.indexOf(page)+1);
}
if (!this.preventChange) {
super.setSelectedIndex(index);
// TF:24/3/08:Tab folders default their focus to the first component in the tab if the tab change was caused
// by a mouse click. We need to defer this event to the end of the EDT queue, even though this method will
// exist on the EDT queue because the tab component hasn't been set up properly here yet, and may still be invisible
if (isSetViaMouse) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Component c = TabFolder.this.getComponentAt(index);
// CraigM:06/08/2008 - Set the traversal reason to be done by the application
ForteKeyboardFocusManager.setApplicationTraversal();
if (c.isFocusable()) {
c.requestFocusInWindow();
}
else {
c.transferFocus();
}
}
});
}
}
this.preventChange = false;
postTabSelect();
EventManager.endEventChain();
}
public void postTabSelect() {
//PM:26/11/07
/*
* Get the actual tab index considering
* invisible tabs
*/
// System.out.println("AfterTabSelect");
int targetIndex = this.getSelectedIndex();
List<JPanel> allPages = this.getAllPages();
if (targetIndex > -1){
JPanel page = (JPanel)this.getComponentAt(targetIndex);
targetIndex = allPages.indexOf(page)+1;
}
// EventManager.startEventChain();
Hashtable<String, Object> qq_Params = new Hashtable<String, Object>();
qq_Params.put( "prevIndex", new ParameterHolder(this.getPreviousTabIndex()));
qq_Params.put( "index", new ParameterHolder(targetIndex) );
qq_Params.put( "page", new ParameterHolder(this.getSelectedComponent()) );
ClientEventManager.postEvent(this, "AfterTabSelect", qq_Params);
// EventManager.endEventChain();
// this.resetPreviousTabIndex();
}
public int getPreviousTabIndex() {
return this.previousTabIndex;
}
public void setPreviousTabIndex(int previousTabIndex) {
this.previousTabIndex = previousTabIndex;
}
public void resetPreviousTabIndex() {
setPreviousTabIndex(getSelectedIndex());
}
public void revertTabSelection(int tabIndex) {
this.ignoreChange = true;
super.setSelectedIndex(tabIndex);
this.resetPreviousTabIndex();
this.ignoreChange = false;
}
public boolean shouldIgnoreChange() {
return this.ignoreChange;
}
public void preventChange(){
this.preventChange = true;
}
/**
* Override the processMouseEvent method so we can set a flag to indicate whether we're coming from
* a mouse click. This is important because Forte set the focus of the tab folder to be the first
* widget within the tab folder if and only if the tab change was caused by a mouse click.
*/
@Override
protected void processMouseEvent(MouseEvent e) {
this.isSetViaMouse = true;
super.processMouseEvent(e);
this.isSetViaMouse = false;
}
//PM:21/07/2008:AXA
public String getUIClassID() {
return uiClassID;
}
//PM:21/07/2008:AXA
public void updateUI() {
setUI(TabFolderUI.createUI(this));
invalidate();
}
//PM:22/07/2008:AXA
@Override
public void addTab(String title, Component component) {
insertTab(title, null, component, null, getAllPages().size());
FrameWeight.set((JComponent)component, Constants.W_NONE);
}
public void focusGained(FocusEvent e) {
EventManager.startEventChain();
int reason = ForteKeyboardFocusManager.getTraversalReason();
if (reason != Constants.FC_SUPRESS) {
Hashtable<String, Object> params = new Hashtable<String, Object>();
params.put("reason", new ParameterHolder(reason));
ClientEventManager.postEvent( this, "BeforeFocusLoss", params );
}
EventManager.endEventChain();
// CraigM:21/08/2008 - This is done in setSelectedIndex
// if (this.getPreviousTabIndex() != this.getSelectedIndex()) {
// this.postTabSelect();
// }
}
public void focusLost(FocusEvent e) {
EventManager.startEventChain();
int reason = ForteKeyboardFocusManager.getTraversalReason();
if (reason != Constants.FC_SUPRESS) {
Hashtable<String, Object> params = new Hashtable<String, Object>();
params.put("reason", new ParameterHolder(reason));
ClientEventManager.postEvent( this, "BeforeFocusLoss", params );
}
EventManager.endEventChain();
}
@SuppressWarnings("unchecked")
public void setPages(Array_Of_Panel pages) {
// add the array of pages
// TF:13/8/07:Ensure we also remove any existing pages, and reset the size of the panel to 1, 1
// or there abouts, as Forte did this when setting the tabs
this.removeAll();
Map<Component, TabInfo> hiddenPages = this.getHiddenPages();
List<JPanel> allPages = this.getAllPages();//PM:14/01/2009:get all pages
allPages.clear();
if (hiddenPages != null) {
hiddenPages.clear();
}
for (int i = 0; i < pages.size(); i++) {
JPanel page = (JPanel) pages.get(i);
TextData caption = Caption.get(page);
// TF:13/8/07:Panels don't have borders in tab panes
Border b = page.getBorder();
if (b instanceof TitledBorder) {
((TitledBorder)b).setTitle("");
((TitledBorder)b).setBorder(new EmptyBorder(0,0,0,0));
}
else {
page.setBorder(null);
}
page.setMinimumSize(null);
page.setPreferredSize(null);
page.setSize(1, 1);
addTab(caption.getValue(), page);
}
this.setSize(1,1);
this.setMinimumSize(null);
this.setPreferredSize(null);
}
/**
* Returns the Swing tab index (which doesn't include hidden tabs), based on the index including hidden tabs.
* CraigM:16/01/2009.
*
* @param modelIndex 0 based index including hidden tabs
* @return 0 based index excluding hidden tabs
*/
public int getTabIndex(int modelIndex) {
List<JPanel> allPages = this.getAllPages();
if (allPages != null && modelIndex >= 0 && modelIndex < allPages.size()) {
JPanel page = allPages.get(modelIndex);
for (int index=0; index<this.getTabCount(); index++) {
if (this.getComponentAt(index) == page) {
return index; // Found the page
}
}
}
return -1;
}
/**
* Returns the model tab index (which includes hidden tabs), based on the swing index excluding hidden tabs.
* CraigM:19/01/2009.
*
* @param tabIndex 0 based index excluding hidden tabs
* @return 0 based index including hidden tabs (-1 if not found)
*/
public int getModelIndex(int tabIndex) {
try {
Component page = this.getComponentAt(tabIndex);
List<JPanel> allPages = this.getAllPages();
return allPages.indexOf(page);
}
catch (IndexOutOfBoundsException e) {
return -1;
}
}
/**
* Get list of all the pages in this tab folder. The pages are returned irrespective
* of their visibility, so that getPages().length != getTabCount() particularly when
* there are invisible pages. The array returned is a copy of the backing array.
* @return
*/
@SuppressWarnings("unchecked")
public Array_Of_Panel<Panel> getPages() {
Array_Of_Panel pages = new Array_Of_Panel();
List<JPanel> allPages = getAllPages();
for (JPanel page : allPages){
pages.add(page);
}
return pages;
}
public void hideTab(JPanel page){
if (isTabVisible(page)){
page.setVisible(false);
page.putClientProperty(IS_TAB_VISIBLE_PROPERTY, false);
List<JPanel> allPages = getAllPages();
Map<Component, TabInfo> hiddenPages = getHiddenPages();
int index = allPages.indexOf(page);
String caption = getTitleAt(indexOfComponent(page));
TabInfo ti = (TabInfo)page.getClientProperty(TAB_INFO_PROPERTY);
if (ti == null) {
ti = new TabInfo(caption, null, index, page);
}
hiddenPages.put(page, ti);
super.remove(page);
_log.debug("Setting Tab Invisible: " + caption );
}
}
public void showTab(JPanel page){
if (!isTabVisible(page)){
page.setVisible(true);//PM:14/01/2009:set to be visible
page.putClientProperty(IS_TAB_VISIBLE_PROPERTY, true);
List<JPanel> allPages = getAllPages();
Map<Component, TabInfo> hiddenPages = getHiddenPages();
TabInfo ti = (TabInfo) hiddenPages.get(page);
//PM:22/07/2008: fixed tab order
int insertIndex = 0;
JPanel lastVisible = null;
for (JPanel tab : allPages){
if (tab == page)
break;
if (isTabVisible(tab))
lastVisible = tab;
}
insertIndex = indexOfComponent(lastVisible) + 1;
super.insertTab(ti.getCaption(), ti.getIcon(), page,
page.getToolTipText(), insertIndex);
hiddenPages.remove(page);
_log.debug("Setting Tab Visible: " + ti.getCaption() );
}
}
public boolean isTabVisible(JPanel jp){
return (Boolean)jp.getClientProperty(IS_TAB_VISIBLE_PROPERTY);
}
}