/* ========================
* JSynoptic : a free Synoptic editor
* ========================
*
* Project Info: http://jsynoptic.sourceforge.net/index.html
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* (C) Copyright 2001-2005, by :
* Corporate:
* EADS Astrium SAS
* EADS CRC
* Individual:
* Claude Cazenave
*
* $Id: SplitTabPane.java,v 1.3 2006/06/08 09:50:21 ogor Exp $
*
* Changes
* -------
* 9 d�c. 2005 : Initial public release (CC);
*
*/
package simtools.ui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import javax.swing.JDialog;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.border.TitledBorder;
/**
* A split tab pane is a JPanel in charge of the layout of several sub panel elements
* 3 kind of layouts are available
* TAB mode : a JTabbedPane is used to display the elements and a card with the element name
* allows to select it
* SPLIT mode : the element is displayed under the JTabbedPane (it one element uses this mode) and
* JSplitPane are used to separate the elements using this mode and thus the user can define the split
* location
* DETACHED mode : the element is not displayed in this panel but in a "detached" dialog. When the user
* closes the dialog the element comes back in this panel with its previous mode (TAB or SPLIT) layout
* All the layout parameters are saved and restore in/from the UserProperties
*/
public class SplitTabPane extends JPanel implements MouseListener, NamedElementContainer {
/** The resources used for the popup menu */
public static MenuResourceBundle resources = (MenuResourceBundle)ResourceFinder.get(SplitTabPane.class);
/** The SPLIT mode id */
public static final int SPLIT = 0;
/** The TAB mode id */
public static final int TAB = 1;
/** The DETACHED mode id */
public static final int DETACHED = 2;
/** The elements list */
protected ArrayList elements;
/** The tab pane, null if no element currently in this mode */
protected JTabbedPane tabPane;
/** The wrapper for the first element in SPLIT mode */
protected SplitBorder firstSplitBorder;
/** The split used between the elements in SPLIT mode and the element in TAB mode */
protected JSplitPane tabSplit;
/** define the panel orientation, i.e. VERTICAL or horizontal */
protected int orientation;
/** the popup to "split" a "tab" element */
protected PopupMenu splitPopup;
/** the popup to "tab" a "split" element */
protected PopupMenu unsplitPopup;
/** allow to split/unsplit
* if not allowed then components stay in their intial mode or can be detached */
protected boolean allowSplit;
/** allow to detach components */
protected boolean allowDetached;
/** the Frame owner used as the owner of the created dialogs in detached mode */
protected Frame owner;
/**
* Creates a new panel without elements
* @param owner used as the owner of the created dialogs in detached mode
* @param orientation JSplitPane.HORIZONTAL_SPLIT or JSplitPane.VERICAL_SPLIT
* @param allowSplit
* @param allowDetached
*/
public SplitTabPane(Frame owner, int orientation,
boolean allowSplit,
boolean allowDetached) {
super(new BorderLayout());
this.owner=owner;
this.orientation = orientation;
this.allowSplit = allowSplit;
this.allowDetached = allowDetached;
elements = new ArrayList();
createPopups();
}
/**
* Checks if an element is visible
* @param name the element name
* @return true if it is visible
*/
public boolean isVisible(String name){
for (int i = 0; i < elements.size(); i++) {
Element e = (Element) elements.get(i);
if (e.name.equals(name)) {
return e.comp.isVisible();
}
}
return false;
}
/**
* Gets the element knowing the displayed component
* @param c the component
* @return the element
*/
public Element getElement(Component c){
return (Element)elements.get(getElementIndex(c));
}
/**
* Gets the element index knowing the displayed component
* @param c the component
* @return the element index
*/
public int getElementIndex(Component c){
for (int i = 0; i < elements.size(); i++) {
Element e = (Element) elements.get(i);
if (e.comp == c) {
return i;
}
}
return -1;
}
/**
* Change the display mode of one element
* @param c the component to change
* @param newMode the new mode
*/
public void changeElement(Component c, int newMode) {
int i = getElementIndex(c);
Element e = (Element) elements.get(i);
int previousMode=e.getMode();
if(previousMode!=newMode){
removeElement(e);
addElement(e.comp, e.name, newMode, i);
getElement(e.comp).setPreviousMode(previousMode);
revalidate();
repaint();
}
}
/* (non-Javadoc)
* @see simtools.ui.NamedElementContainer#addElement(java.awt.Component, java.lang.String)
*/
public void addElement(Component comp, String name) {
addElement(comp, name, TAB);
}
/**
* Add a new element at the last index
*
* @param c the component to be displayed
* @param name its name
* @param mode the mode to be used
*/
public void addElement(Component c, String name, int mode) {
addElement(c, name, mode, elements.size());
}
/**
* Add a new element at the given index
* @param c the component to be displayed
* @param name its name
* @param mode the mode to be used
* @param index the index in the list of exisiting elements
*/
protected void addElement(Component c, String name, int mode, int index) {
if (mode == SPLIT) {
if (firstSplitBorder == null) {
firstSplitBorder = createSplitBorder(c, name);
if (tabPane != null) { // there is alrady one tab pane
removeAll();
tabSplit = new JSplitPane(orientation, tabPane, firstSplitBorder);
elements.add(index, createElement(c, name, tabSplit,firstSplitBorder));
add(tabSplit);
} else { // we dont need a split yet
elements.add(index, createElement(c, name, firstSplitBorder,firstSplitBorder));
add(firstSplitBorder);
}
} else {
// there is split area, add this panel to it
Element p=getElement(firstSplitBorder.comp);
if (p.owner == p.border) {
// first split to be created
removeAll();
SplitBorder sb=createSplitBorder(c, name);
JSplitPane sp= new JSplitPane(orientation, p.border, sb);
p.owner = sp;
elements.add(index, createElement(c, name, sp,sb));
add(sp);
}
else{
JSplitPane prev=(JSplitPane)p.owner;
while(prev.getRightComponent() instanceof JSplitPane){
// go to latest split
prev=(JSplitPane)prev.getRightComponent();
}
SplitBorder right=(SplitBorder)prev.getRightComponent();
p=getElement(right.comp);
if(p.owner!=prev){ // cross check
throw new RuntimeException(
"SplitTabPane unexpected state : invalid owner "+p.comp+" "+p.owner);
}
SplitBorder sb=createSplitBorder(c, name);
JSplitPane split = new JSplitPane(orientation, right,
sb);
p.owner = split;
elements.add(index, createElement(c, name, split,sb));
prev.setRightComponent(split);
}
}
} else if(mode==TAB) {
if (tabPane == null) {
tabPane = new JTabbedPane(
orientation == JSplitPane.HORIZONTAL_SPLIT ? JTabbedPane.LEFT
: JTabbedPane.TOP);
tabPane.addMouseListener(this);
if (firstSplitBorder == null) {
// we dont need a split yet
removeAll();
add(tabPane);
} else {
removeAll();
Element e=getElement(firstSplitBorder.comp);
tabSplit = new JSplitPane(orientation, tabPane, e.owner);
if(e.owner == e.border){
e.owner=tabSplit;
}
add(tabSplit);
}
}
// compute the tabIndex
int tabIndex = 0;
for (int i = index - 1; i >= 0; i--) {
Element p = (Element) elements.get(i);
if (p.owner == tabPane) {
tabIndex = tabPane.indexOfComponent(p.comp) + 1;
break;
}
}
tabPane.insertTab(name, null, c, null, tabIndex);
elements.add(index, createElement(c, name, tabPane,null));
}
else if(mode==DETACHED){
JDialog jd=createDialog(c,name);
elements.add(index, createElement(c, name, jd,null));
}
}
/**
* Remove an element from the list
* The removed element is not foreseen to be reused
* @param e the element to be removed
*/
protected void removeElement(Element e) {
if (e.owner == tabPane) {
tabPane.remove(e.comp);
if (tabPane.getTabCount() == 0) {
// remove the tabPane
tabPane = null;
removeAll();
if(firstSplitBorder!=null){
Element ef=getElement(firstSplitBorder.comp);
Component c=ef.owner;
if(tabSplit==c){
add(ef.border);
ef.owner=ef.border;
}
else{
add(c);
}
}
tabSplit = null;
}
} else if (e.owner == e.border) {
removeAll();
firstSplitBorder=null;
} else if (e.owner instanceof JSplitPane) {
JSplitPane sp = (JSplitPane) e.owner;
if ((sp.getLeftComponent() instanceof SplitBorder)
&& ((SplitBorder) (sp.getLeftComponent())).comp == e.comp) {
Component r=sp.getRightComponent();
if(sp.getParent() instanceof JSplitPane){
JSplitPane previous = (JSplitPane)sp.getParent();
sp.removeAll();
previous.setRightComponent(r);
if(r instanceof SplitBorder){
SplitBorder b=(SplitBorder)r;
Element eb=(Element)getElement(b.comp);
if(previous.getLeftComponent()==tabPane){
firstSplitBorder=b;
}
eb.owner=previous;
}
else if(r instanceof JSplitPane){
}
else {
throw new RuntimeException(
"SplitTabPane unexpected state : right component = "+r);
}
}
else if(sp.getParent()==this){
sp.removeAll();
removeAll();
add(r);
if(r instanceof SplitBorder){
SplitBorder b=(SplitBorder)r;
Element eb=(Element)getElement(b.comp);
eb.owner=b;
firstSplitBorder=b;
}
else if(r instanceof JSplitPane){
firstSplitBorder=(SplitBorder)((JSplitPane)r).getLeftComponent();
}
else {
throw new RuntimeException(
"SplitTabPane unexpected state : right component = "+r);
}
}
else{
throw new RuntimeException(
"SplitTabPane unexpected state : root split is not owned by this");
}
} else if ((sp.getRightComponent() instanceof SplitBorder)
&& ((SplitBorder) (sp.getRightComponent())).comp == e.comp) {
Component left = sp.getLeftComponent();
if (left instanceof JSplitPane) {
throw new RuntimeException(
"SplitTabPane unexpected state : left element is a split");
} else {
if (left == tabPane) {
// remove the last split
removeAll();
if (tabPane != null) {
add(tabPane);
}
if (firstSplitBorder.comp != e.comp) {
throw new RuntimeException(
"SplitTabPane unexpected state : last split is inconsistent");
}
firstSplitBorder = null;
tabSplit = null;
} else {
if(left instanceof SplitBorder){
SplitBorder b=(SplitBorder)left;
Element eb=(Element)getElement(b.comp);
sp.removeAll();
if(sp.getParent()==this){
removeAll();
add(left);
firstSplitBorder=(SplitBorder)left;
eb.owner=b;
}
else if(sp.getParent() instanceof JSplitPane){
JSplitPane parent=(JSplitPane)sp.getParent();
parent.setRightComponent(left);
if(parent.getLeftComponent()==tabPane){
firstSplitBorder=(SplitBorder)left;
}
eb.owner=parent;
}
else{
throw new RuntimeException(
"SplitTabPane unexpected state : invalid parent ="+sp.getParent());
}
}
else {
throw new RuntimeException(
"SplitTabPane unexpected state : left="+left);
}
}
}
} else {
throw new RuntimeException(
"SplitTabPane unexpected state : inconsistent owner ="+e.owner+"\n"+
"Left="+sp.getLeftComponent()+"\n"+
"Right="+sp.getRightComponent());
}
} else if(e.owner instanceof JDialog){
JDialog jd=(JDialog)e.owner;
jd.setVisible(false);
jd.getContentPane().removeAll();
}
else {
throw new RuntimeException(
"SplitTabPane unexpected state : invalid owner");
}
e.border=null;
e.owner=null;
elements.remove(e);
}
/**
* Create the popups that allows the user to change elements layout mode
* To be oveverridden if needed !
*/
protected void createPopups() {
if(allowSplit || allowDetached){
splitPopup = new PopupMenu(TAB);
unsplitPopup = new PopupMenu(SPLIT);
}
}
/**
* Create the dialog used in DETACHED mode
* To be oveverridden if needed !
* @param c the component to be displayed
* @param name the name used as a dialog title
* @return the dialog
*/
protected JDialog createDialog(final Component c, String name){
JDialog jd=new JDialog(owner, name);
jd.getContentPane().add(c);
jd.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
jd.addWindowListener(new WindowAdapter(){
public void windowClosed(WindowEvent e) {
Element el=getElement(c);
changeElement(c,el.previousMode);
}
});
jd.pack();
jd.show();
return jd;
}
public void save(UserProperties up, String suffix){
String propRoot=SplitTabPane.class.getName();
if(suffix!=null){
propRoot=propRoot+"."+suffix;
}
for(int i=0;i<elements.size();i++){
Element e=(Element)elements.get(i);
e.save(up, propRoot);
}
}
public void load(UserProperties up, String suffix){
String propRoot=SplitTabPane.class.getName();
if(suffix!=null){
propRoot=propRoot+"."+suffix;
}
for(int i=0;i<elements.size();i++){
Element e=(Element)elements.get(i);
e.load(up, propRoot);
}
}
//=========================================
// mouse listener interface
//=========================================
public void mouseClicked(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) == MouseEvent.BUTTON3_MASK) {
if (e.getSource() == tabPane) {
splitPopup.show(tabPane, e.getX(), e.getY());
} else {
SplitBorder sb=(SplitBorder) e.getSource();
unsplitPopup.comp=sb.comp;
unsplitPopup.show(sb, e.getX(), e.getY());
}
}
}
// -------------------------------------------------------
// inner classes
// -------------------------------------------------------
/**
* Create the panel used in SPLIT mode to display the component inside
* JSplitPane.
* To be oveverridden if needed !
* @param c the component to be displayed
* @param name the name used as a dialog title
* @return the panel
*/
protected SplitBorder createSplitBorder(Component c, String name){
return new SplitBorder(c,name);
}
/**
* The default wrapper for the components when in SPLIT mode
* A JPanel with a TitledBorder containing the element name
* This panel has also a mouse listener for the popup menu
*/
protected class SplitBorder extends JPanel {
protected final Component comp;
protected SplitBorder(Component c, String name) {
super(new BorderLayout());
comp = c;
add(comp);
TitledBorder b = new TitledBorder(name);
setBorder(b);
addMouseListener(SplitTabPane.this);
}
/**
* @return Returns the comp.
*/
public Component getComp() {
return comp;
}
}
/**
* Create the Element, the internal structure describing the managed components
* To be oveverridden if needed !
* @param c the component to be displayed
* @param n the name of the component
* @param o the container owner of this component (a JSplitPane, a JDialog, a JTabbedPane or this)
* @parame b the wrapper in SPLIT mode
* @return the element
*/
protected Element createElement(Component c, String n, Container o, SplitBorder b) {
return new Element(c,n,o,b);
}
/**
* Element is the internal structure describing the managed components
* @return the element
*/
protected class Element {
/** the component to be displayed */
final Component comp;
/** the wrapper in SPLIT mode */
SplitBorder border;
/** the container owner of this component (a JSplitPane, a JDialog, a JTabbedPane or this) */
Container owner;
/** the name of the component */
final String name;
/** the previous mode of this component used when the dialog is closed in detached mode */
int previousMode=TAB;
/**
* Creates a new element
* @param c the component to be displayed
* @param n the name of the component
* @param o the container owner of this component (a JSplitPane, a JDialog, a JTabbedPane or this)
* @parame b the wrapper in SPLIT mode
*/
protected Element(Component c, String n, Container o, SplitBorder b) {
comp = c;
owner = o;
name = n;
border=b;
}
/**
* @return Returns the previous mode.
*/
public int getPreviousMode() {
return previousMode;
}
/**
* @param previousMode The previous mode to set.
*/
public void setPreviousMode(int previousMode) {
this.previousMode = previousMode;
}
/**
* @return Returns the name.
*/
public String getName() {
return name;
}
/**
* @return Returns the owner.
*/
public Container getOwner() {
return owner;
}
/**
* @return the element mode
*/
public int getMode(){
if(owner instanceof JDialog){
return DETACHED;
}
else if(owner instanceof JTabbedPane){
return TAB;
}
else{
return SPLIT;
}
}
public String getPropertyName(String propRoot){
return propRoot+"."+name.replaceAll("[ \t]","_");
}
public void save(UserProperties up, String propRoot){
propRoot=getPropertyName(propRoot);
up.setInt(propRoot+".MODE", getMode());
up.setInt(propRoot+".PREVIOUS_MODE", getPreviousMode());
if(getMode()==SPLIT){
if(owner instanceof JSplitPane){
up.setInt(propRoot+".DIVIDER", ((JSplitPane)owner).getDividerLocation());
}
}
else if(getMode()==DETACHED){
JDialog jd=(JDialog)owner;
up.setInt(propRoot+".X", jd.getX());
up.setInt(propRoot+".Y", jd.getY());
up.setInt(propRoot+".WIDTH", jd.getWidth());
up.setInt(propRoot+".HEIGHT", jd.getHeight());
}
}
public void load(UserProperties up, String propRoot){
propRoot=getPropertyName(propRoot);
int mode=up.getInt(propRoot+".MODE",-1);
if(mode<0){
return;
}
previousMode=up.getInt(propRoot+".PREVIOUS_MODE",previousMode);
Element e=this;
if(mode!=getMode()){
changeElement(comp,mode);
e=getElement(comp);
}
if(e.getMode()==SPLIT){
if(e.owner instanceof JSplitPane){
((JSplitPane)e.owner).setDividerLocation(
up.getInt(propRoot+".DIVIDER", 10));
}
}
else if(e.getMode()==DETACHED){
JDialog jd=(JDialog)e.owner;
int x=up.getInt(propRoot+".X",0);
int y=up.getInt(propRoot+".Y", 0);
int w=up.getInt(propRoot+".WIDTH", 100);
int h=up.getInt(propRoot+".HEIGHT", 100);
jd.setSize(w,h);
jd.validate();
jd.setLocation(x,y);
}
}
}
/**
* The default popup used to provide control on the layout mode
*/
public class PopupMenu extends JPopupMenu implements ActionListener {
private JMenuItem _miChange;
private JMenuItem _miDetached;
private int mode;
Component comp;
/**
* Constructs a new PopupMenu instance
*/
public PopupMenu(int mode) {
this.mode = mode;
if(allowSplit){
add(_miChange = resources.getItem((mode == SPLIT ? "unsplit" : "split"), this));
}
if(allowDetached){
add(_miDetached = resources.getItem("detached", this));
}
}
//
// ActionListener interface
//
public void actionPerformed(ActionEvent e) {
if (e.getSource() == _miChange) {
if (mode == TAB) {
changeElement(tabPane.getSelectedComponent(), SPLIT);
} else {
changeElement(comp, TAB);
}
}
else if (e.getSource() == _miDetached) {
if (mode == TAB) {
changeElement(tabPane.getSelectedComponent(), DETACHED);
} else {
changeElement(comp, DETACHED);
}
}
}
}
}