Package net.xoetrope.swing.docking

Source Code of net.xoetrope.swing.docking.XDockableHeader

package net.xoetrope.swing.docking;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.lang.reflect.Method;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.TransferHandler;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.plaf.basic.BasicButtonUI;

/**
* <p>A header like panel that paints a gradient fill and displays a caption. The
* header also contains a minimize button that initiates docking. The header may
* also be double clicked to zoom in on the owner XDockingPanel and the header
* may also be dragged from one docking panel to another.</p>
* <p>
* The colours of the header are controlled with the <code>dockingHeader</code>
* style and <code>dockingHeader/active</code> for the active tab colours
* </p>
* <p>Copyright: Xoetrope Ltd. (c) 2003-2006<br>
* License:      see license.txt</p>
* $Revision: 1.2 $
*/
public class XDockableHeader extends JLabel implements MouseListener, MouseMotionListener, ActionListener
{
  /**
   * The default header button size
   */
  public static final int BUTTON_SIZE = 8;
 
  private static final int NORMAL = 0;
  private static final int ROLLOVER = 1;
  private static final int PRESSED = 2;
  private static final int NUM_BUTTON_STATES = 3
 
  public static final int MINIMIZE = 0;
  public static final int ZOOM = 1;
  public static final int CLOSE = 2;
  public static final int RESTORE = 3;
  public static final int NUM_IMAGE_TYPES = 4;

  private static Image[] buttonImages;
  private static Color activeColor, activeTextColor, pressedTextColor, headerTextColor, headerBkColor;
  private static boolean useGradientHeaders = true;

  private MouseEvent firstMouseEvent = null;
 
  private XDockable dockable;
  private boolean active;
  private Container glassPane;
 
  private JButton sysMinimizeBtn, sysCloseBtn, sysZoomBtn;
  private JPanel buttonPanel;

  private Method clipIfNecessary;
 
  private int minHeaderHeight = 2;
 
  /**
   * Creates a new instance of XDockableHeader
   * @param dockable the dockable object that to which this header contributes.
   * @param translator the translator component or null if the tooltips are not translated
   * @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 XDockableHeader( XDockable dockable, Color[] colors, String[] tooltips )
  {
    this.dockable = dockable;
   
    if ( dockable.icon != null ) {
      int iconHeight = dockable.icon.getIconHeight();
      minHeaderHeight = Math.max( iconHeight + 6, minHeaderHeight );
    }   

    active = true;
   
    if ( colors != null ) {
      headerBkColor = colors[ 0 ];
      headerTextColor = colors[ 1 ];
      activeColor = colors[ 2 ];
      activeTextColor = colors[ 3 ];
      pressedTextColor = colors[ 4 ];
    }
    else {
      headerBkColor = dockable.header.headerBkColor;
      headerTextColor = dockable.header.headerTextColor;
      activeColor = dockable.header.activeColor;
      activeTextColor = dockable.header.activeTextColor;
      pressedTextColor = dockable.header.pressedTextColor;
    }

    if ( tooltips == null ) {
      tooltips = new String[ NUM_IMAGE_TYPES -1 ];
      tooltips[ 0 ] = dockable.header.sysMinimizeBtn.getToolTipText();
      tooltips[ 1 ] = dockable.header.sysZoomBtn.getToolTipText();
      tooltips[ 2 ] = dockable.header.sysCloseBtn.getToolTipText();
    }

    setLayout( new BorderLayout());
    setBorder( new EmptyBorder( 1, 3, 1, 3 ));
    buttonPanel = new JPanel();
    buttonPanel.setLayout( new FlowLayout());
    buttonPanel.setOpaque( false );
   
    if ( buttonImages == null )
      buttonImages = new Image[ NUM_IMAGE_TYPES * 3 ];

    buttonPanel.add( sysMinimizeBtn = new JButton());
    setButtonProperties( sysMinimizeBtn, MINIMIZE, tooltips[ MINIMIZE ], dockable.canMinimize );
   
    buttonPanel.add( sysZoomBtn = new JButton());
    setButtonProperties( sysZoomBtn, ZOOM, tooltips[ ZOOM ], dockable.canZoom );

    buttonPanel.add( sysCloseBtn = new JButton());
    setButtonProperties( sysCloseBtn, CLOSE, tooltips[ CLOSE ], dockable.canClose );

    add( buttonPanel, BorderLayout.EAST );

    setTransferHandler( new XDockableTransferHandler( dockable.dockedContainer ));
    addMouseListener( this );
    addMouseMotionListener( this );
  }
 
  /**
   * Configure the header buttons
   * @param headerBtn the button
   * @param itemId the item index
   * @param tooltip the tooltip text
   * @param isVisible true if the button is visible
   */
  protected void setButtonProperties( JButton headerBtn, int itemId, String tooltip,  boolean isVisible )
  {
    headerBtn.setToolTipText( tooltip );

    headerBtn.setIcon( new ImageIcon( getImage( itemId, NORMAL )));
    headerBtn.setPressedIcon( new ImageIcon( getImage( itemId, PRESSED )));
    headerBtn.setRolloverIcon( new ImageIcon( getImage( itemId, ROLLOVER )));

    headerBtn.setPreferredSize( new Dimension( BUTTON_SIZE, BUTTON_SIZE ));
    headerBtn.setBackground( headerBkColor );
    headerBtn.setOpaque( false );
    headerBtn.setBorderPainted( false );
    headerBtn.setBorder( new EmptyBorder( 0, 0, 0, 0 ));
    headerBtn.setUI( new BasicButtonUI());   
    headerBtn.addActionListener( this );

    headerBtn.setVisible( isVisible );
  }

  /**
   * Respond to the 'minimize' button and dock this panel. The panel is 'docked'
   * into the sidebar specified in the constructor. Delegates to the owner
   * XDockingPanel panel to remove the content
   * @param e the mouse event
   */
  public void actionPerformed( ActionEvent e )
  { 
    if ( e.getSource() == sysZoomBtn ) {
      int newState = RESTORE;
 
      if ( isZoomed())
        newState = ZOOM;
     
      setZoomState( newState );
      zoomPanel();
    }
    else
      dockable.dockedContainer.removeDockable( dockable, ( e.getSource() == sysMinimizeBtn ));
  }
 
  /**
   * Get the dockable object that this header is associated with.
   * @return the managing dockable object
   */
  public XDockable getDockable()
  {
    return dockable;
  }
 
  /**
   * Mark this header as active or inactive. When active a highligh is shown
   * along with a minimize/dock button
   */
  public void setActive( boolean state )
  {
    getComponent( 0 ).setVisible( state );
    active = state;
    repaint();
  }
 
  /**
   * Control the docking component's ability to close
   * @param state true to allow closing, false to prevent closing
   */
  public void setCanClose( boolean state )
  {
    dockable.canClose = state;
    sysCloseBtn.setVisible( state );
  }

  /**
   * Determine if the docking component can close
   * @return true if the component can close
   */
  public boolean getCanClose()
  {
    return dockable.canClose;
  }
 
  /**
   * Control the docking component's ability to minimize
   * @param state true to allow closing, false to prevent minimizing
   */
  public void setCanMinimize( boolean state )
  {
    dockable.canMinimize = state;
    sysMinimizeBtn.setVisible( state );
  }

  /**
   * Determine if the docking component can minimize
   * @return true if the component can minimize
   */
  public boolean getCanMinimize()
  {
    return dockable.canMinimize;
  }
 
  /**
   * Control the docking component's ability to dock. Once the component is
   * minimized it will not be able to dock back into the container
   * if this parameter is <code>false</code>
   * @param state true to allow closing, false to prevent docking
   */
  public void setCanDockClose( boolean state )
  {
    dockable.canDock = state;
  }

  /**
   * Determine if the docking component can dock
   * @return true if the component can dock
   */
  public boolean getCanDock()
  {
    return dockable.canDock;
  }
   
  /**
   * Control the docking component's ability to be dragged and dropped.
   * if this parameter is <code>false</code>
   * @param state true to allow dragging, false to prevent dragging
   */
  public void setCanDrag( boolean state )
  {
    dockable.canDrag = state;
  }

  /**
   * Determine if the docking component can be dragged
   * @return true if the component can be dragged
   */
  public boolean getCanDrag()
  {
    return dockable.canDrag;
  }
 
   
  /**
   * Control the docking component's ability to be zoomed.
   * if this parameter is <code>false</code>
   * @param state true to allow zooming, false to prevent zooming
   */
  public void setCanZoom( boolean state )
  {
    dockable.canZoom = state;
  }

  /**
   * Determine if the docking component can be zoomed
   * @return true if the component can be zoomed
   */
  public boolean getCanZoom()
  {
    return dockable.canZoom;
  }

  /**
   * If a border has been set on this component, returns the
   * border's insets; otherwise calls <code>super.getInsets</code>.
   *
   * @return the value of the insets property
   * @see #setBorder
   */
  public Insets getInsets()
  {
    return new Insets( 2, 4, 2, 4 );
  }
 
  /**
   * Set the state of the zoome/restore button
   * @param newState the ZOOM or RESTORE state
   */
  public void setZoomState( int newState )
  {
    sysZoomBtn.setIcon( new ImageIcon( getImage( newState, NORMAL )));
    sysZoomBtn.setPressedIcon( new ImageIcon( getImage( newState, PRESSED )));
    sysZoomBtn.setRolloverIcon( new ImageIcon( getImage( newState, ROLLOVER )));
  }
 
  /**
   * Zoom in on this panel, maximizing it so that it consumes the entire
   * dockin apnel
   */
  public void zoomPanel()
  {
    XCardPanel cardPanel = dockable.getCardPanel();
    if ( cardPanel != null ) {
      cardPanel.swapViews( dockable );

      dockable.header.setZoomState( cardPanel.isZoomed() ? RESTORE : ZOOM );
      dockable.dockedContainer.fireDockingPanelListeners( cardPanel.isZoomed() ? XDockingPanel.MAXIMIZED : XDockingPanel.RESTORED );   
    }
  }
 
  /**
   * Is the panel zoomed?
   * @return true if it is zoomed.
   */
  public boolean isZoomed()
  {
    XCardPanel cardPanel = dockable.getCardPanel();
    if ( cardPanel != null )
      return cardPanel.isZoomed();   
   
    return false;
  }
 
  /**
   * If the <code>preferredSize</code> has been set to a
   * non-<code>null</code> value just returns it.
   * If the UI delegate's <code>getPreferredSize</code>
   * method returns a non <code>null</code> value then return that;
   * otherwise defer to the component's layout manager.
   *
   * @return the value of the <code>preferredSize</code> property
   * @see #setPreferredSize
   * @see ComponentUI
   */
  public Dimension getPreferredSize()
  {
    Dimension sz = super.getPreferredSize();
    return new Dimension( Math.max( 20, sz.width ), Math.min( 35, Math.max( minHeaderHeight, sz.height )));
  }

  /**
   * Paint the component, drawing the background gradient and the active
   * indicator if the header is active/selected
   * @param g the graphics context
   */
  public void paintComponent( Graphics g )
  {
    Rectangle rect = getBounds();

    // Fill the background
    Color bkColor = headerBkColor;
    g.setColor( bkColor );
    if ( useGradientHeaders && ( g instanceof Graphics2D )) {
      Graphics2D g2d = ((Graphics2D)g);
      float dx = rect.height/4.0F;
      dx = dx * dx / (float)rect.width;
      GradientPaint gradient = new GradientPaint( 0.0F, 0.0F, brightenColor( bkColor, 110 ),//.brighter(),
                                                  dx,
                                                  (float)3.0F*rect.height/4.0F,
                                                  brightenColor( bkColor, 90 ),//.darker(),
                                                  true );
      g2d.setPaint( gradient );
      g2d.fill( new Rectangle( 0, 0, rect.width, rect.height ));
      g2d.draw3DRect( 0, 0, rect.width-1, rect.height-1, true );
    }
    else
      g.fillRect( 0, 0, rect.width, rect.height );

    if ( active ) {
      /** @todo find the system color for the tab/button highlights */
      /** @todo draw/replace the border */
      int red = activeColor.getRed();
      int green = activeColor.getGreen();
      int blue = activeColor.getBlue();
      g.setColor( activeColor.brighter());
      g.drawLine( 0, 0, rect.width-1, 0 );
      g.drawLine( rect.width-1, 0, rect.width-1, 2 );
      g.drawLine( 0, 0, 0, 2 );
      g.setColor( activeColor );
      g.setColor( new Color( red, green, blue, 223 ));
      g.drawLine( 1, 1, rect.width-2, 1 );
      g.setColor( new Color( red, green, blue, 128 ));
      g.drawLine( 1, 2, rect.width-2, 2 );
    }
   
    int iconWidth = 0;
    if ( dockable.icon != null ) {
      int iconHeight = dockable.icon.getIconHeight();
      g.drawImage( dockable.icon.getImage(), 5, ( rect.height - iconHeight ) / 2, this );
      iconWidth = 8 + dockable.icon.getIconWidth();
    }
    else {
      for ( int i = 0; i < 3; i++ ) {
        g.setColor( new Color( 255, 255, 255, 200 ));
        g.fillRect( 4, 6 + i*4, 2, 2 );
        g.setColor( new Color( 0, 0, 0, 128 ));
        g.fillRect( 3, 5 + i*4, 2, 2 );
      }     
      iconWidth = 10;
    }
   
    FontMetrics fm = g.getFontMetrics();
    g.setColor( headerTextColor );
    Shape oldClip = g.getClip();
    int captionWidth = rect.width - ( iconWidth + ( active ? ( buttonPanel.getWidth() + 4 ) : 0 ));
    g.setClip( iconWidth, 0, captionWidth, rect.height );
    String caption = getText();
   
    caption = clipStringIfNecessary( this, fm, caption, captionWidth );
    g.drawString( caption, iconWidth, rect.height / 2 + fm.getDescent() + 1 );
    g.setClip( oldClip );
 
 
  /**
   * Invoked when a mouse button has been pressed on a component.
   */
  public void mousePressed( MouseEvent e )
  {
    //Don't bother to drag if there is no image.
    firstMouseEvent = e;
    //e.consume();
  }   
 
  /**
   * Invoked when the mouse button has been clicked (pressed
   * and released) on a component. If a double click has been detected then the
   * outer XCardPanel will swap views to and from the zoom view.
   */
  public void mouseClicked( MouseEvent e )
  {
    if ( e.getClickCount() == 2 )
      zoomPanel();   
  }
 
  /**
   * Invoked when the mouse enters a component.
   */
  public void mouseEntered(MouseEvent e) {}
 
  /**
   * Invoked when the mouse exits a component.
   */
  public void mouseExited(MouseEvent e) {}
  /**
   * Invoked when a mouse button is pressed on a component and then
   * dragged.  <code>MOUSE_DRAGGED</code> events will continue to be
   * delivered to the component where the drag originated until the
   * mouse button is released (regardless of whether the mouse position
   * is within the bounds of the component).
   * <p>
   * <p>Initiates a drag of the docked panel</p>
   * Due to platform-dependent Drag&Drop implementations,
   * <code>MOUSE_DRAGGED</code> events may not be delivered during a native
   * Drag&Drop operation. 
   */
  public void mouseDragged( MouseEvent e )
  {
    if ( firstMouseEvent != null ) {
      e.consume();

      //If they are holding down the control key, COPY rather than MOVE
      int action = TransferHandler.MOVE;

      int dx = Math.abs( e.getX() - firstMouseEvent.getX());
      int dy = Math.abs( e.getY() - firstMouseEvent.getY());
      //Arbitrarily define a 5-pixel shift as the
      //official beginning of a drag.
      if ((( dx > 5 ) || ( dy > 5 )) &&  dockable.canDrag ) {
        //This is a drag, not a click.
        JComponent c = (JComponent)e.getSource();
        TransferHandler handler = c.getTransferHandler();
        //Tell the transfer handler to initiate the drag.
        handler.exportAsDrag( c, firstMouseEvent, action );
        firstMouseEvent = null;
     
        glassPane = (Container)getRootPane().getGlassPane();
        glassPane.setVisible( true );
        dockable.dockedContainer.addDragProxies( glassPane );
      }
    }
  }
 
  /**
   * Invoked when the mouse cursor has been moved onto a component
   * but no buttons have been pushed.
   */
  public void mouseMoved( MouseEvent e )
  {   
  }

  /**
   * Invoked when a mouse button has been released on a component.
   */
  public void mouseReleased(MouseEvent e)
  {
    endDock();
    dockable.dockedContainer.setActivateHeader( this );
  }
 
  /**
   * Adjust the position of the drop area preview during a drag operation
   * @param target the potential drop site
   */
  public void showDock( JComponent c, Container target )
  {
    if ( glassPane != null ) {
      Component[] children = glassPane.getComponents();
      int numChildren = children.length;
      for ( int i = 0; i < numChildren; i++ ) {
        Component child = children[ i ];
        ((JComponent)child).setBorder( child == c ? new LineBorder( new Color( 255, 0, 0, 128 ), 3 ) : null );
      }
    }
  }
 
  /**
   * End the drag and drop operation by removing the drop site preview indicator
   */
  public void endDock()
  {
    if ( glassPane != null ) {
      glassPane.removeAll();
      glassPane.setVisible( false );
      glassPane = null;
    }
    firstMouseEvent = null;
  }
 
  /**
   * Create a minimize button image
   * @param state 0 - the normal minimize icon image, 1 - the rollover image, 2 - the pressed image
   * @param zoomed true for the zoomed state
   * @return the image for the minimize button
   * @todo change this image for a proper 'dock'/minimize icon
   */
  private Image getImage( int imageType, int state )
  {
    int imageIdx = imageType * 3 + state;
    if ( buttonImages[ imageIdx ] == null ) {
      buttonImages[ imageIdx ] = new BufferedImage( BUTTON_SIZE, BUTTON_SIZE, BufferedImage.TYPE_INT_ARGB );
      Graphics2D g2d = (Graphics2D)buttonImages[ imageIdx ].getGraphics();
      Object hint = g2d.getRenderingHint( RenderingHints.KEY_RENDERING );
      g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
      g2d.setRenderingHint( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY );
      g2d.setStroke( new BasicStroke( 1.0F ));

      for ( int i = 0; i < 2; i++ ) {     
        int offset = i == 0 ? 0 : -1;
        int opacity = i == 0 ? 64 : 255;
        Color c;
        if ( state == ROLLOVER )
          c = activeTextColor;
        else if ( state == PRESSED )
          c = pressedTextColor;
        else
          c = headerTextColor;
       
        int red = c.getRed();
        int green = c.getGreen();
        int blue = c.getBlue();
        g2d.setColor( new Color( red, green, blue, opacity ));

        if ( imageType == MINIMIZE ) {
          RoundRectangle2D.Double rect = new RoundRectangle2D.Double( 1 + offset, 1 + offset + BUTTON_SIZE-4, BUTTON_SIZE-2, 2, 2, 2 );
          g2d.draw( rect );
        }
        else if ( imageType == CLOSE ) {
          g2d.drawLine( 1 + offset, 1 + offset, BUTTON_SIZE - 1 + offset, BUTTON_SIZE - 1 + offset );
          g2d.drawLine( BUTTON_SIZE - 1 + offset, 1 + offset, 1 + offset, BUTTON_SIZE - 1 + offset );
        }
        else if ( imageType == ZOOM ) {
          RoundRectangle2D.Double rect = new RoundRectangle2D.Double( 1 + offset, 1 + offset, XDockableHeader.BUTTON_SIZE-2, XDockableHeader.BUTTON_SIZE -2, 2, 2 );
          g2d.draw( rect );
        }
        else if ( imageType == RESTORE ) {
          RoundRectangle2D.Double rect = new RoundRectangle2D.Double( 1 + offset, 3 + offset, XDockableHeader.BUTTON_SIZE-3, 4, 2, 2 );
          g2d.draw( rect );

          rect = new RoundRectangle2D.Double( 2 + offset, 1 + offset, XDockableHeader.BUTTON_SIZE-3, 4, 2, 2 );
          g2d.draw( rect );
        }
      }
      g2d.setRenderingHint( RenderingHints.KEY_RENDERING, hint );
      g2d.dispose();
    }
    return buttonImages[ imageIdx ];
  }
 
  /**
   * Toggle the drawing of gradients in the headers
   * @param state false to turn off gradient painting
   */
  public static void setUseGradientHeaders( boolean state )
  {
    useGradientHeaders = state;
  }
 
  /**
   * Get an brighter version of a color
   * @param color the original color
   * @param percentage the percentage of the original color brightness to return
   */
  public static Color brightenColor( Color color, int percentage )
  {
    if ( percentage == 100 )
      return color;
   
    float[] hsb = new float[ 3 ];
    color.RGBtoHSB( color.getRed(), color.getGreen(), color.getBlue(), hsb );
    return new Color( color.HSBtoRGB( hsb[ 0 ], hsb[ 1 ], Math.min( 1.0F, (( percentage * hsb[ 2 ] ) / 100.0F ))));
  }
 
  public String clipStringIfNecessary( JComponent c, FontMetrics fm,
                                               String caption,
                                               int availTextWidth )
  {
    if (( caption == null ) || ( caption.length() == 0 ))
      return "";
   
    try {
      /* This is a hack, using a private class so expect it to break when the JDK
       * is upgraded */
      Class klass = Class.forName( "sun.swing.SwingUtilities2" );
      Class params[] = new Class[ 4 ];
      params[ 0 ] = JComponent.class;
      params[ 1 ] = FontMetrics.class;
      params[ 2 ] = String.class;
      params[ 3 ] = int.class;
      clipIfNecessary = klass.getMethod( "clipStringIfNecessary", params );
      if ( clipIfNecessary != null ) {
        Object args[] = new Object[ 4 ];
        args[ 0 ] = this;
        args[ 1 ] = fm;
        args[ 2 ] = caption;
        args[ 3 ] = new Integer( availTextWidth );
        caption = (String)clipIfNecessary.invoke( null, args );
        return caption;
      }
    }
    catch ( Exception ex )
    {
      ex.printStackTrace();
    }
   
    return "";
  }
}
TOP

Related Classes of net.xoetrope.swing.docking.XDockableHeader

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.