/*
Copyright (c) 2003-2009 ITerative Consulting Pty Ltd. All Rights Reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:
o Redistributions of source code must retain the above copyright notice, this list of conditions and
the following disclaimer.
o Redistributions in binary form must reproduce the above copyright notice, this list of conditions
and the following disclaimer in the documentation and/or other materials provided with the distribution.
o This jcTOOL Helper Class software, whether in binary or source form may not be used within,
or to derive, any other product without the specific prior written permission of the copyright holder
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
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);
// TF:12/07/2010:(Thanks for VVH): Changed the index to "index" instead of getTabCount()
// super.insertTab(title, icon, component, tip, getTabCount());
super.insertTab(title, icon, component, tip, index);
} 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);
}
}