Package net.xoetrope.swing.docking

Source Code of net.xoetrope.swing.docking.XDockingPanel$XDockableProxy

package net.xoetrope.swing.docking;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.SystemColor;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.jdesktop.swingx.MultiSplitLayout;
import org.jdesktop.swingx.MultiSplitLayout.Leaf;
import org.jdesktop.swingx.MultiSplitLayout.Node;
import org.jdesktop.swingx.MultiSplitLayout.Split;
import org.jdesktop.swingx.JXMultiSplitPane;

/**
* <p>A panel for use in a docking framework. The panel is an area into which
* various content panels may be docked. These docked panels are displayed with
* a header containing a title and a dock/minimize button.
* Clicking on the minimize button caused the panel to hide itself within the
* MultiSplitPane's layout and add a button to the side bar if one has been
* specified. Double clicking the header causes the panel to occupy the
* full area of its parent</p>
* <p>An individual XDockingPanel can contain multiple child panels, each of
* which is given a separate header, similar to a tab in a tab pane. The header
* contains a button, which when clicked docks the panel to a sidebar. The header
* may also contain a highlight to indicate that it is selected/active</p>
* <p>The dockable components are managed via the XDockable object which contains
* a collection of the participating objects, including the XDockingPanel, the
* XDockingSidebar that holds the minimized panel and the XDockableHeader that
* is used within this panel. The value of the dockable object's fields are
* set as the dockable panel's state is changed.</p>
* <p>In docking a panel the panel is hidden and the sidebar is informed that it
* should assume ownership of the dockable. However the content/panel remains
* a child of this docking panel unless shown in a preview panel. In the preview
* the content is 'borrowed' by the preview panel, but when the preview panel
* is dismissed the content is restored to this instance of the docking panel.</p>
* <p>Copyright: (c) Xoetrope Ltd., 1998-2008<br>
* License:      see license.txt
* @version $Revision: 1.2 $
*/
public class XDockingPanel extends JPanel
{
  public static final boolean USE_REMOVE_STRATEGY = false;
 
  public static final int CLOSED = 0;
  public static final int MAXIMIZED = 1;
  public static final int MINIMIZED = 2;
  public static final int PREVIEW_CLOSED = 3;
  public static final int PREVIEW_OPENED = 4;
  public static final int RESTORED = 5;

  protected XDockableHeader activeHeader;

  protected JPanel contentPane;
  protected JPanel headerPanel;
  protected Dimension defSize;
//  private XDockingSideBar sidebar;
  private CardLayout contentManager;
 
  private String constraint; 
 
//  private static Rectangle emptyRect = new Rectangle( 0, 0, 0, 0 );
 
  protected JXMultiSplitPane splitPane; 
 
  protected ArrayList listeners;
 
  /**
   * Creates a new instance of XDockingPanel
   *
   * @param constraint the name/constraint by which this panel is known. Note
   * that this name is not displayed to the user and is instead used
   * programatically
   */
  public XDockingPanel( String constraint )
  {
    this.constraint = constraint;
    listeners = new ArrayList();
    setName( constraint );   
     
    setBackground( SystemColor.control );
    setLayout( new BorderLayout());   
    setTransferHandler( new XDockableTransferHandler( this ));
   
    headerPanel = new JPanel();
    headerPanel.setLayout( new GridLayout( 1, 0 ));
       
    add( headerPanel, BorderLayout.NORTH );
   
    add( contentPane = new JPanel(), BorderLayout.CENTER );
    contentPane.setOpaque( true );
    contentPane.setBackground( Color.white );
   
    // Use a card layout so that only one of the content panels is visible
    // The one that corresponds to the selected header.
    contentPane.setLayout( contentManager = new CardLayout());
  }
 
  /**
   * Add a listener to this panel
   */
  public void addDockingPanelListener( XDockingPanelListener l )
  {
    listeners.add( l );
  }

  /**
   * Remove a listener from this panel
   */
  public void removeDockingPanelListener( XDockingPanelListener l )
  {
    listeners.remove( l );
  }
 
  /**
   * Invoke a dockingpanel listener method
   * @param state a flasg indicating the new state and hence the method to invoke
   */
  public void fireDockingPanelListeners( int state )
  {
    int numListeners = listeners.size();
    for ( int i = 0; i < numListeners; i++ ) {
      XDockingPanelListener l = (XDockingPanelListener)listeners.get( i );
      switch ( state ) {
        case CLOSED:  l.panelClosed()break;
        case MAXIMIZED:  l.panelMaximized()break;
        case MINIMIZED:  l.panelMimimized()break;
        case PREVIEW_CLOSED:  l.panelPreviewClosed()break;
        case PREVIEW_OPENED:  l.panelPreviewOpened()break;
        case RESTORED:  l.panelRestored()break;
      }   
    }
  }
 
  /**
   * <p>Add proxies for each draggable target area in the split pane. If you wish
   * to change this behavior and define areas that are not already in the
   * split pane then overload this method.</p>
   * <p>The method temporarily adds instances
   * of XDockableProxy to the glass pane while the drag operation is in progress.
   * Once the drag operation is complete the proxies are removed.</p>
   * @param glassPane the glass pane.
   */
  void addDragProxies( Container glassPane )
  {
    glassPane.setLayout( null );
    splitPane = ((JXMultiSplitPane)getParent());
    if ( splitPane != null ) {
      ArrayList proxies = new ArrayList();
      Component[] children = splitPane.getComponents();
      for ( int i = 0; i < children.length; i++ ) {
        Component child = children[ i ];
        if ( child instanceof XDockingPanel ) {
          XDockableProxy proxy = new XDockableProxy( (XDockingPanel)child );
          Point p = SwingUtilities.convertPoint( child, 0, 0, glassPane );
          proxy.setLocation( p );
          proxy.setVisible( true );
          proxy.setSize( child.getSize());
          proxy.setTransferHandler( new XDockableTransferHandler( this ));
          proxies.add( proxy );
        }
      }
     
      // Sort the proxies by size so that most can be selected and so that the
      // big areas do not hide the small ones, particularly when there is only
      // one area visible.
      Collections.sort( proxies, new Comparator() {
        public int compare( Object proxyA, Object proxyB ) {
          Rectangle rectA = ((XDockableProxy)proxyA).getBounds();
          Rectangle rectB = ((XDockableProxy)proxyB).getBounds();
          if ( rectA.intersects( rectB )) {
            int a = ( rectA.width * rectA.height );
            int b = ( rectB.width * rectB.height );
            if ( a < b )
              return -1;
            else if ( a > b )
              return 1;
            else
              return 0;
          }
          else if ( rectA.contains( rectB ))
            return 1;
          else if ( rectA.equals( rectB ))
            return 0;
         
          return -1;
        }
      });
     
      int numProxies = proxies.size();
      for ( int j = 0; j < numProxies; j++ ) {
        XDockableProxy child = (XDockableProxy)proxies.get( j );
        glassPane.add( child );
      }
    }
  }
 
  private void addChildAreas( ArrayList rects, Node n )
  {
    if ( n instanceof Leaf )
      rects.add( n.getBounds());
    else if ( n instanceof Split ) {
      List children = ((Split)n).getChildren();
      for ( int i = 0; i < children.size(); i++ ) {
        Node nc = (Node)children.get( i );
        addChildAreas( rects, nc );
      }
    }
  }
 
  /**
   * Get the constraint used by this docking panel.
   */
  public String getConstraint()
  {
    return constraint;
  }
 
  /**
   * Set the constraint used by this docking panel.
   * @param c the new layout constraint
   */
  public void setConstraint( String c )
  {
    constraint = c;
  }
 
  /**
   * Get the container into which the content should be added
   * @return the content pane
   */
  public Container getContentPane()
  {
    return contentPane;
  }
 
  /**
   * Dock the content of the dockable object back into this container. The
   * dockable object is updated in the process to refelct its new ownership.
   * @param dockable the object being docked.
   */
  public void restoreContent( XDockable dockable )
  {
    int numHeaders = headerPanel.getComponentCount();
    for ( int i = 0; i < numHeaders; i++ )
      ((XDockableHeader)headerPanel.getComponent( i )).setActive( false );

    contentPane.add( dockable.content, dockable.getId() );
    contentManager.show( contentPane, dockable.getId() );
    setVisible( true );
    restoreDividers( dockable.dockedContainer );
   
    headerPanel.add( dockable.header );
    dockable.header.setZoomState( dockable.header.ZOOM );
    dockable.header.setVisible( true );
    dockable.header.setActive( true );
    activeHeader = dockable.header;

    splitPane.revalidate();
    fireDockingPanelListeners( RESTORED );
  }
 
  /**
   * Add a panel to this container. The panel will have a tab showing the
   * title. The dockable object is updated to reflect its new ownership
   * @param dockable the object being docked.
   * @param colors the header colors: background, text color, active background, active text color
   * @param tooltips the tooltip text for the minimize and close buttons
   */
  public void addDockable( XDockable dockable, Color[] colors, String[] tooltips )
  {
    if ( activeHeader != null )
      activeHeader.setActive( false );
    dockable.dockedContainer = this;

    XDockableHeader header = null;
    if ( dockable.header == null )
      header = new XDockableHeader( dockable, colors, tooltips );   
    else
      header = dockable.header;
    header.setText( dockable.title );
    header.setToolTipText( dockable.title );

    dockable.header = header;
    headerPanel.add( header );

    /** @todo     remove this hack, its probably no longer needed - insetad use
     * getConstraint */
    dockable.content.setName( dockable.constraint );
    contentPane.add( dockable.content, dockable.getId() );
    contentManager.show( contentPane, dockable.getId() );
   
    activeHeader = header;
    activeHeader.setActive( true );
  }
 
  /**
   * Set the active header
   * @param dh the new active header
   */
  public void setActivateHeader( XDockableHeader dh )
  {
    if ( activeHeader != null )
      activeHeader.setActive( false );

    activeHeader = dh;
    XDockable activeDockable = activeHeader.getDockable();
    contentManager.show( contentPane, activeDockable.getId());
    activeHeader.setActive( true );
 
   
  /**
   * Remove the content referred to by the dockable from this docking panel
   * @param dockable the object being docked.
   * @param addToSideBar true to add the content to the dockable's sidebar
   * @param removeDocked true to remove a docked component from the sidebar if
   * the component is already minimized and docked
   */
  public void removeDockable( XDockable dockable, boolean addToSidebar, boolean removeDocked )
  {
    removeDockable( dockable, addToSidebar );
    if ( removeDocked && ( dockable.dockingSideBar != null ))
      dockable.dockingSideBar.removeDockable( dockable );
  }
 
  /**
   * Remove the content referred to by the dockable from this docking panel
   * @param dockable the object being docked.
   * @param addToSideBar true to add the content to the dockable's sidebar
   */
  public void removeDockable( XDockable dockable, boolean addToSidebar )
  {
    activeHeader = dockable.header;
    Container cont = getParent();
    if ( !( cont instanceof JXMultiSplitPane )) {
      while ( !( cont instanceof XCardPanel ))
        cont = cont.getParent();
      ((XCardPanel)cont).swapViews( dockable );
    }
    splitPane = ((JXMultiSplitPane)getParent());
    int activePos = getComponentZOrder( headerPanel, activeHeader );
    if ( activePos > 0 )
      activePos--;
    headerPanel.remove( activeHeader );
    headerPanel.revalidate();

    boolean hasVisibleHeaders = headerPanel.getComponentCount() > 0;
    headerPanel.doLayout();
    headerPanel.repaint();
   
    if ( !hasVisibleHeaders ) {
      if ( USE_REMOVE_STRATEGY ) {
        splitPane.remove( this );
        splitPane.repaint();
      }
      else {
        setVisible( false );
        hideDividers( dockable.dockedContainer );
      }
    }
   
    if ( addToSidebar )
      dockable.dockingSideBar.add( dockable );
   
    contentManager.removeLayoutComponent( dockable.content );
    contentPane.remove( dockable.content );
    activeHeader = null;
    if ( hasVisibleHeaders ) {
      if ( activePos >= 0 )
        activeHeader = (XDockableHeader)headerPanel.getComponent( activePos );
      if ( activeHeader != null ) {
        contentManager.show( contentPane, activeHeader.getDockable().getId());
        activeHeader.setActive( true );
      }
    }
   
    revalidate();
    splitPane.revalidate();
   
    fireDockingPanelListeners( addToSidebar ? MINIMIZED : CLOSED );   
 
 
  /**
   * Dock this panel
   */
  public void dock()
  {
    removeDockable( activeHeader.getDockable(), true );
  }

//  /**
//   * Remove the 'docked' panel from the side bar and restore it to its original
//   * location within the layout
//   */
//  public void restore()
//  {
//    if ( USE_REMOVE_STRATEGY )
//      splitPane.add( this, constraint );
//    else 
//      setVisible( true );
//    restoreDividers( null );
//
//    revalidate();
//    splitPane.revalidate();
//    fireDockingPanelListeners( RESTORED );
//  }
 
  /**
   * Get the window title of teh active header
   * @return the title
   */
  public String getTitle()
  {
    return activeHeader.getText();
  }
 
  /**
   * Set the window title of the active header
   * @param title the new title
   */
  public void setTitle( String title )
  {
    activeHeader.setText( title );
    activeHeader.setToolTipText( title );
  }

  /**
   * Get the preferred size of the component. The
   * @return the size of the panel or 0x0 if the panel is docked
   */
  public Dimension getPreferredSize()
  {
    if ( defSize != null )
      return defSize;
   
    return super.getPreferredSize();
  }
  
  /**
   * Hide dividers that are not required due to one of the nodes being non visible / hidden.
   * Checks for dividers where the associated component is not visible and set the
   * visible state of that node to false. For other nodes the visible state is
   * set to true.
   * @param comp the component that has just been hidden
   */
  private void hideDividers( Component comp )
  {
    MultiSplitLayout layout = (MultiSplitLayout)splitPane.getLayout();
    MultiSplitLayout.Node node = layout.getNodeForComponent( comp );
    if ( node != null ) {
      MultiSplitLayout.Split p = node.getParent();
      p.hide( node );
      if ( !p.isVisible())
        p.getParent().hide( p );
     
      p.checkDividers( p );
      // If the split has become invisible then the parent may also have a divider
      // that needs to be hidden.
      while ( !p.isVisible()) {
        p = p.getParent();
        if ( p != null )
          p.checkDividers( p );
        else
          break;       
      }
    }
    layout.setFloatingDividers( false );
  }
 
  /**
   * Restore dividers that were not required due to one of the nodes being non visible / hidden.
   * Checks for dividers where two adjacent nodes are not separated by a visible divider
   * @param comp the component that has just been shown
   */
  private void restoreDividers( Component comp )
  {
    MultiSplitLayout layout = (MultiSplitLayout)splitPane.getLayout();
    MultiSplitLayout.Node node = layout.getNodeForComponent( comp );
    if ( node != null ) {
      node.setVisible( true );
      MultiSplitLayout.Split p = node.getParent();
      p.restoreDividers( p );
    }
    layout.setFloatingDividers( false );
 
  /**
   * Returns the z-order index of the component inside the container.
   * The higher a component is in the z-order hierarchy, the lower
   * its index.  The component with the lowest z-order index is
   * painted last, above all other child components.
   *
   * @param comp the component being queried
   * @return  the z-order index of the component; otherwise
   *          returns -1 if the component is <code>null</code
   *          or doesn't belong to the container
   */
  private int getComponentZOrder( Container cont, Component comp )
  {
    if ( comp == null ) {
        return -1;
    }
   
    synchronized( getTreeLock()) {
      // Quick check - container should be immediate parent of the component
      if ( comp.getParent() != cont )
        return -1;

      int ncomponents = cont.getComponentCount();
      for ( int i = 0; i < ncomponents; i++ ) {
        if ( cont.getComponent( i ) == comp )
          return i;
      }           
    }
    // To please javac
    return -1;
  }
 
 
  /**
   * A proxy for hidden panels. The proxy is used during drag and drop operations
   * and is temporarily placed on the glass pane.
   */
  public class XDockableProxy extends JLabel
  {
    private XDockingPanel dockingPanel;

    /**
     * Create a new proxy
     * @param dp the panel being proxied
     */
    XDockableProxy( XDockingPanel dp )
    {
      dockingPanel = dp;
    }
   
    /**
     * Get the original panel being proxied.
     */
    public XDockingPanel getDockingPanel()
    {
      return dockingPanel;
    }
  }
}
TOP

Related Classes of net.xoetrope.swing.docking.XDockingPanel$XDockableProxy

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.