Package net.xoetrope.awt

Source Code of net.xoetrope.awt.XTableRenderer

package net.xoetrope.awt;

import java.awt.AWTEventMulticaster;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.ItemSelectable;
import java.awt.ScrollPane;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import net.xoetrope.xui.XProjectManager;
import net.xoetrope.xui.data.XDataBinding;
import net.xoetrope.xui.data.XModel;
import net.xoetrope.xui.data.XTextBinding;
import net.xoetrope.xui.style.XStyle;
import java.awt.Rectangle;
import java.util.Hashtable;

/**
* <p>Provides a simple read-only tables/grid component.</p>
* <p>Copyright (c) Xoetrope Ltd., 1998-2004<br>
* License:      see license.txt
* $Revision: 2.3 $
*/
public class XTableRenderer extends Canvas implements MouseListener, KeyListener, ItemSelectable
{
  private int[] colWidth;
  private int colPadding = 2;
  private int currentY = 0;
  private int itemIdx;
  private XModel model;
  private Font font;
  private FontMetrics fontMetrics;
  private int fontHeight;
  private int rowHeight;
  private int headerHeight;
  private String tableStyle, headerStyle, selectedStyle;
  private Color backColor, foreColor, darkerColor;
  private static final double darkerFactor = 0.95;
  private boolean updateModelSelection;

  private boolean interactiveTable;
  private boolean drawFrame;
  private int selectedRow;
  private int selectedColumn;
  private int startRow;
  private XTable owner;
  private boolean drawBorder;
  private Color borderColor;

  private static final int VK_KP_UP          = 0xE0;
  private static final int VK_KP_DOWN        = 0xE1;
//  private static final int VK_KP_LEFT        = 0xE2;
//  private static final int VK_KP_RIGHT       = 0xE3;
  private Object[] components;
  private Component currentComponent = null;
  private XDataBinding editBinding;
  private boolean rendered = false;

  /**
   * Create a new table renderer
   * @param parent the parent container/table
   */
  public XTableRenderer( XTable parent )
  {
    interactiveTable = false;
    drawFrame = false;
    selectedRow = 0;
    selectedColumn = 0;
    startRow = 0;
    fontHeight = 13;

    addMouseListener( this );
    addKeyListener( this );

    owner = parent;
  }

  /**
   * Set the XModel which we will be generating the table from
   * @param xmodel The XModel of data
   */
  public void setContent( XModel xmodel )
  {
    model = xmodel;
    if ( model != null ) {
      int numChildren = model.get( 0 ).getNumChildren();
      components = new Object[ numChildren ];
      colWidth = new int[ numChildren ];
      for ( int i = 0; i < colWidth.length; i++ )
        colWidth[ i ] = 100;
    }
    else
      components = null;
  }

  /**
   * Set the type of component for a column
   * @param col the field index
   * @param compName the component name
   */
  public void setComponentAt( int col, String compName )
  {
    components[ col ] = compName;
  }

  /**
   * Set the general style of the XTable
   * @param style XStyle
   */
  public void setStyle( String style )
  {
    if ( style == null )
      tableStyle = "base";
    else
      tableStyle = style;
  }

  /**
   * Set the style of the header data
   * @param style XStyle
   */
  public void setHeaderStyle( String style )
  {
    headerStyle = style;
  }

  /**
   * Set the style of the selected row
   * @param style XStyle
   */
  public void setSelectedStyle( String style )
  {
    selectedStyle = style;
  }

  /**
   * Set the style for the border
   * @param styleName the style name
   */
  public void setBorderStyle( String styleName )
  {
    XStyle style = XProjectManager.getStyleManager().getStyle( styleName );

    borderColor = style.getStyleAsColor( XStyle.COLOR_FORE );
    drawBorder = true;
  }

  /**
   * Check the if the table is interactive
   * @return true if the table supports user interaction
   */
  public boolean isInteractiveTable()
  {
    return interactiveTable;
  }

  /**
   * Set the user interaction state
   * @param state true for an user interactive table.
   */
  public void setInteractiveTable( boolean state )
  {
    interactiveTable = state;
    repaint();
  }

  /**
   * Draw an individual row of data. Alternate the backcolor for every other
   * row. Set the clip region and fill the background. Loop each XModel in
   * the model parameter drawing the values and drawing a vertical line to the
   * right of the cell. Draw a line under the row
   * @param row The index of the row being rendered
   * @param g The Graphics object
   * @param model The XModel containing the row of data
   */
  private void renderRow( int row, Graphics g, XModel model, boolean headerCell )
  {
    int currentX = drawFrame ? 1 : 0 ;
    clearRow( g, row );

    for ( int fieldIdx = 0; fieldIdx < model.getNumChildren(); fieldIdx++ ){
      if ( model.get( fieldIdx )!=null ){
        try {
          currentX = renderCell( g, currentX, model.get( fieldIdx ), (String)model.get( fieldIdx ).get(), fieldIdx, headerCell );
        }
        catch ( Exception ex ) {
          ex.printStackTrace();
        }
      }
    }
  }

  /**
   * Render a table cell
   * @param g The graphics context
   * @param currentX the X position
   * @param value the value to render
   * @param fieldIdx the field index
   * @param headerCell true if a header cell is being rendered
   * @return the new X position
   */
  private int renderCell( Graphics g, int currentX, XModel model, String value, int fieldIdx, boolean headerCell )
  {
    Rectangle clipRect = g.getClipBounds();
    int clipY = currentY - fontMetrics.getAscent() - colPadding;
    int clipW = clipRect.width - ( currentX - clipRect.x );
    int clipH = Math.min( clipRect.height - ( clipY - clipRect.y ), getSize().height );
    g.setColor( foreColor );
    g.setClip( currentX, clipY, Math.min( colWidth[ fieldIdx ] - ( colPadding * 2 ), clipW ), clipH );
    if ( value != null )
      g.drawString( value, currentX + colPadding, currentY );

    g.setColor( borderColor );
    g.setClip( currentX, clipY, Math.min( getSize().width, clipW ), clipH );

    if ( drawBorder )
    g.drawRect( currentX
                , currentY - fontMetrics.getAscent() - colPadding
                , colWidth[ fieldIdx ] - 1
                , fontHeight + ( colPadding * 2 ));

    // Draw a vertical line to the right of the cell
    g.drawLine( currentX + colWidth[ fieldIdx ] - 1
                , currentY - fontMetrics.getAscent() - colPadding
                , currentX + colWidth[ fieldIdx ] - 1
                , currentY + ( colPadding * 2 ) + 1 );
    currentX += colWidth[ fieldIdx ];
    g.setClip( clipRect );

    return currentX;
  }

  /**
   * Erase the backgroudn of a row
   * @param g The graphics context
   * @param row the row number
   */
  private void clearRow( Graphics g, int row )
  {
    if ( row % 2 == 1 )
      g.setColor( darkerColor );
    else
      g.setColor( backColor );

    int offset = drawFrame ? 1 : 0;
    g.setClip( offset, offset, getSize().width - 1 - offset, getSize().height );
    g.fillRect( offset, currentY - fontMetrics.getAscent() - colPadding, getSize().width,
                fontHeight + ( colPadding * 2 ) );
  }

  /**
   * Get the parent element from the XModel. Apply the header style increment
   * the currentY and draw the first row of data in the XModel. Apply the general
   * style, loop thru the remaining elements in the XModel and render them.
   * @param g The graphics object
   */
  private void render( Graphics g )
  {
    rendered = true;
    if ( model != null ) {
      itemIdx = 0;
      int startClipY = ( int )g.getClipBounds().y;
      int endClipY = ( int )( g.getClipBounds().height + g.getClipBounds().y );

      // Render the header
      startRow = renderHeader( g, model, startClipY );
      headerHeight = currentY + ( colPadding * 2 ) + 1;

      // Render the content
      int numChildren = model.getNumChildren();
      applyStyle( g, tableStyle );
      if ( borderColor == null )
        borderColor = getForeground().darker();

      int currentRow = getCurrentRow();
      for ( int rowIdx = startRow; rowIdx < numChildren; rowIdx++ ) {
        if ( interactiveTable && ( rowIdx == currentRow ) )
          applySelectedStyle( g, selectedStyle );

        currentY += fontHeight + ( colPadding * 2 );
        XModel rowModel = model.get( rowIdx );
        if ( rowModel != null ){
          if ( ( ( String )rowModel.get( 0 ).get() ).length() > 0 )
            itemIdx++;

          if ( currentY > ( startClipY - ( fontHeight + ( colPadding * 2 ) ) ) )
            renderRow( itemIdx, g, rowModel, false );

            // Reapply the normal style
          if ( interactiveTable && ( rowIdx == currentRow ) )
            applyStyle( g, tableStyle );
          endClipY = ( int )( g.getClipBounds().height + g.getClipBounds().y );
          if ( currentY > endClipY )
            break;
        }
      }

      if (( currentRow >= 0 ) && ( currentRow < numChildren ))
        model.get( currentRow );
    }
  }

  /**
   * Renders the table header
   * @param g  The graphics object
   * @param model The data model
   */
  private int renderHeader( Graphics g, XModel model, int startClipY )
  {
    applyStyle( g, headerStyle );

    if ( model.getNumChildren() > 0 ) {
      XModel rowModel = model.get( 0 );
      String tag = rowModel.getTagName();
      if ( tag.equalsIgnoreCase( "th" ) ) {
        // A <th>...</th> header record
        currentY += fontMetrics.getAscent() + colPadding;
        if ( startClipY < rowHeight )
          renderRow( 0, g, rowModel, true );
        return 1;
      }
      else if ( tag.equalsIgnoreCase( "tr" ) ) {
        // No header specified in the XML
        return 0;
      }
      else {
        // An extended model node type e.g. XLib::XTableModelNode
        currentY += fontMetrics.getAscent() + colPadding;
        if ( startClipY >= rowHeight )
          return 0;

        int currentX = 0;
        clearRow( g, 0 );

        for ( int fieldIdx = 0; fieldIdx < model.getNumAttributes(); fieldIdx++ )
          currentX = renderCell( g, currentX, model, model.getAttribName( fieldIdx ), fieldIdx, true );

          // Draw a line under the row
        g.drawLine( 1, currentY + ( colPadding * 2 ) + 1, getSize().width,
                    currentY + ( colPadding * 2 ) + 1 );
      }
    }

    return 0;
  }

  /**
   * Initialise the currentY coordinate and call the render function with the
   * Graphics object. When finished draw a line around the XTable.
   * @param g the graphics context
   */
  public void paint( Graphics g )
  {
    update( g );
  }

  /**
   * Applies a named style to the Graphics context.
   * @param styleName
   * @param g
   */
  private void applyStyle( Graphics g, String styleName )
  {
    if ( styleName == null )
      styleName = "base";

    XStyle style = XProjectManager.getStyleManager().getStyle( styleName );

    foreColor = style.getStyleAsColor( XStyle.COLOR_FORE );
    if ( foreColor == null )
      foreColor = getForeground();

    backColor = style.getStyleAsColor( XStyle.COLOR_BACK );
    if ( backColor == null )
      backColor = getBackground();

    if ( backColor != null ) {
      darkerColor = new Color(
          ( int ) ( backColor.getRed() * darkerFactor ),
          ( int ) ( backColor.getGreen() * darkerFactor ),
          ( int ) ( backColor.getBlue() * darkerFactor ) );
    }

    font = XProjectManager.getStyleManager().getFont( style );
    g.setFont( font );
    fontMetrics = g.getFontMetrics();
    fontHeight = fontMetrics.getHeight();
    rowHeight = ( fontHeight + ( colPadding * 2 ) );
  }

  /**
   * Applies a named style to the Graphics context.
   * @param styleName
   * @param g
   */
  private void applySelectedStyle( Graphics g, String styleName )
  {
    if ( styleName == null )
      styleName = "base";

    XStyle style = XProjectManager.getStyleManager().getStyle( styleName );

    foreColor = style.getStyleAsColor( XStyle.COLOR_FORE );
    if ( foreColor == null )
      foreColor = getForeground();

    backColor = style.getStyleAsColor( XStyle.COLOR_BACK );
    if ( backColor == null )
      backColor = getBackground();
    darkerColor = backColor;
  }

  /**
   * Sets the indexof the selected row
   * @param idx the new selected row
   */
  public void setSelectedRow( int idx )
  {
    selectedRow = Math.max( 0, Math.min( idx, model.getNumChildren()-1 ));
    syncModel();
    repaint();
  }

  /**
   * Get the index of the selected row
   * @return the index of the selected row
   */
  public int getSelectedRow()
  {
    return selectedRow;
  }

  /**
   * Tie the model selection to this table's selection
   * @param doUpdate true to tie the selections together, false to ignore
   */
  public void setUpdateModelSelection( boolean doUpdate )
  {
    updateModelSelection = doUpdate;
    syncModel();
  }

  /**
   * Update the underlying model's selection index with the tables index.
   */
  private void syncModel()
  {
    if ( updateModelSelection )
      model.get( startRow + selectedRow );
  }

  /**
   * Handles the mouse click by changeing the selected row.
   * @param e the mouse event
   */
  public void mouseClicked( MouseEvent e )
  {
  }

  /**
   * Mouse event handler for mouse enter event
   * @param e the mouse event
   */
  public void mouseEntered( MouseEvent e )
  {
  }

  /**
   * Mouse event handler for mouse exit event
   * @param e the mouse event
   */
  public void mouseExited( MouseEvent e )
  {
  }

  /**
   * Mouse event handler for mouse pressed event
   * @param e the mouse event
   */
  public void mousePressed( MouseEvent e )
  {
  }

  /**
   * Mouse event handler for mouse released event
   * @param e the mouse event
   */
  public void mouseReleased( MouseEvent e )
  {
    removeCurrentComponent();

    rowHeight = ( fontHeight + ( colPadding * 2 ) );
    int oldSelection = selectedRow;
    int y = e.getY();
    int x = e.getX();
    y -= headerHeight;
    if ( model != null ) {
      int maxRow = model.getNumChildren() - 1;
      selectedRow = Math.min( y / rowHeight, maxRow );

      int maxCol = components.length;
      selectedColumn = 0;
      int xMouse = x;
      for ( int i = 0; i < maxCol; i++ ) {
        xMouse -= colWidth[ i ];
        if ( xMouse > 0 )
          selectedColumn++;
        else
          break;
      }

      if ( selectedColumn < components.length ) {
        if ( components[ selectedColumn ] != null ) {
          setCellComponent( selectedColumn, selectedRow );
        }
      }

      repaint( 0, headerHeight + rowHeight * oldSelection, 1000, rowHeight );
      repaint( 0, headerHeight + rowHeight * selectedRow, 1000, rowHeight );

      if ( itemListener != null )
        itemListener.itemStateChanged( new ItemEvent( owner, ItemEvent.ITEM_STATE_CHANGED, new Integer( selectedRow ),
            ItemEvent.SELECTED ) );
    }
    syncModel();
  }

  /**
   * Remove the current component
   */
  public void removeCurrentComponent()
  {
    if ( currentComponent != null )
      owner.getComponentPanel().remove( currentComponent );
  }

  /**
   * Check to see if there is a value in the components array at position 'col'
   * If so create an instance of the component and add it to the componentPanel
   * Create a binding to the model and set it to the local variable 'editBinding'
   * @param col The column of the model which was selected
   * @param row The row of the model which was selected
   */
  private void setCellComponent( int col, int row )
  {
    try {
      int x = 0;
      for ( int i = 0; i < col; i++ )
        x += colWidth[ i ];
      int width = colWidth[ col ] - colPadding;
      int y = rowHeight + ( rowHeight * row );
      int height = rowHeight;
      if ( ( row + 1 ) < model.getNumChildren() ) {
        Component c = (Component)Class.forName( components[ col ].toString().trim() ).newInstance();
        c.addKeyListener( this );
        c.setBounds( x, y, width, height );
        Container panel = owner.getComponentPanel();
        panel.add( c, 0 );
        currentComponent = c;
        XModel rowModel = ( XModel )model.get( row + 1 );
        XModel bindModel = ( XModel )rowModel.get( col );
        Hashtable bindingConfig = new Hashtable();
        Hashtable instanceConfig = new Hashtable();
        XTextBinding binding = new XTextBinding();
        binding.setup( XProjectManager.getCurrentProject(), c, bindingConfig, instanceConfig );
        binding.setSource( bindModel );
        binding.setOutput( bindModel, null );
        binding.get();
        editBinding = binding;
      }
    }
    catch ( Exception ex ) {
      ex.printStackTrace();
    }
  }

  /**
   * Key event handler or the key press event
   * @param e the event
   */
  public void keyPressed( KeyEvent e )
  {
    if ( !e.getSource().equals( currentComponent ) ) {
      if ( model != null ) {
        int oldSelection = selectedRow;
        int numChildren = model.getNumChildren();
        int keyCode = e.getKeyCode();
        ScrollPane sp = ( ( ScrollPane )getParent().getParent() );
        int y = ( int )sp.getScrollPosition().y;
        if ( ( keyCode == e.VK_UP ) || ( keyCode == VK_KP_UP ) ) {
          selectedRow = Math.max( 0, selectedRow - 1 );
          sp.setScrollPosition( 0, y - rowHeight );
        }
        else if ( ( keyCode == e.VK_DOWN ) || ( keyCode == VK_KP_DOWN ) ) {
          selectedRow = Math.min( numChildren - 1, selectedRow + 1 );
          sp.setScrollPosition( 0,
                                Math.min( y + rowHeight, ( ( numChildren - 2 ) * rowHeight ) ) );
        }
        else
          return;

        repaint( 0, headerHeight + rowHeight * oldSelection, 1000, rowHeight );
        repaint( 0, headerHeight + rowHeight * selectedRow, 1000, rowHeight );

        if ( itemListener != null )
          itemListener.itemStateChanged( new ItemEvent( owner,
              ItemEvent.ITEM_STATE_CHANGED, new Integer( selectedRow ),
              ItemEvent.SELECTED ) );
      }
    }
    syncModel();
  }

  /**
   * Key event handler or the key typed event
   * @param e the event
   */
  public void keyTyped( KeyEvent e )
  {
  }

  /**
   * Key event handler or the key released event
   * @param e the event
   */
  public void keyReleased( KeyEvent e )
  {
    if ( e.getSource().equals( currentComponent ) )
      editBinding.set();
  }

  /**
   * Update the display
   * @param g the graphics context
   */
  public void update( Graphics g )
  {
    if ( drawFrame )
      g.drawRect( 0, 0, getSize().width - 1, getSize().height - 1 );

    currentY = 0;
    render( g );
   // g.drawRect( 0, 0, getSize().width - 1, getSize().height - 1 );
  }

  /**
   * Calculate the size of the content. This method is called from within the
   * paint method and recalculates the required size for display of the content.
   * If a scrollpane is the parent then this control is resized so that all the
   * content  will be visible. The scrollpane may initially have no scrollbar so
   * to avoid flicker and multiple repaints as the control is sized and offscreen
   * graphics context is used for the sizing.
   * @return The table size
   */
  public Dimension calcSize()
  {
    Dimension d = new Dimension();

    if (( model != null ) && ( model.getNumChildren() > 0 )) {
      // This width should be calculated based on the content of the cells.
      XModel rowModel = model.get( 0 );
      int numCols = rowModel.getNumChildren();
      for ( int i = 0; i < numCols; i++ )
        d.width += colWidth[ i ];

      Graphics g = getGraphics();
      if ( g != null ) {
        if ( tableStyle != null ) {
          // This doesn't properly account for different font sizes in the header but
          // a little more or less height probably won't matter much.
          XStyle style = XProjectManager.getStyleManager().getStyle( tableStyle );
          font = XProjectManager.getStyleManager().getFont( style );
        }
        g.setFont( font );
        fontMetrics = g.getFontMetrics();
        fontHeight = fontMetrics.getHeight();
        g.dispose();
      }

      d.height = ( ( fontHeight + ( colPadding * 2 ) + 1 ) ) * ( model.getNumChildren() + 1 ) + 10;
    }
    return d;
  }

  int getNumRows()
  {
    if ( model != null )
      return model.getNumChildren();
    return 0;
  }

  /**
   * Set the table column width.
   * @param fieldIdx the field index
   * @param w the new column width
   */
  public void setColWidth( int fieldIdx, int w )
  {
    colWidth[ fieldIdx ] = w;
    Dimension d = calcSize();
    setBounds( 0, 0, ( int )d.getSize().width, ( int )d.getSize().height );
    owner.getComponentPanel().setBounds( 0, 0, ( int )d.getSize().width, ( int )d.getSize().height );
  }

  /**
   * Gets the index in the model of the currently selected row.
   * @return the row offset
   */
  public int getCurrentRow()
  {
    return startRow + selectedRow;
  }

  /**
   * Gets the offset in the model of the first row of data. This takes account
   * of how the table header is stored in the model. In the static data or XML
   * representations the header is recorded with a row of <TH> elements, whereas
   * when the data has originated in a database then a custom node type may be
   * used instead.
   * @return the row offset
   */
  public int getFirstRow()
  {
    return startRow;
  }

  /**
   * Adds the specified item listener to receive item events from
   * this list.  Item events are sent in response to user input, but not
   * in response to calls to <code>select</code> or <code>deselect</code>.
   * If listener <code>l</code> is <code>null</code>,
   * no exception is thrown and no action is performed.
   *
   * @param         l the item listener
   * @see           #removeItemListener( ItemListener )
   * @see           java.awt.event.ItemEvent
   * @see           java.awt.event.ItemListener
   * @since         JDK1.1
   */
  public synchronized void addItemListener(ItemListener l) {
      if ( l == null ) {
          return;
      }
      itemListener = AWTEventMulticaster.add( itemListener, l );
  }

  /**
   * Removes the specified item listener so that it no longer
   * receives item events from this list.
   * If listener <code>l</code> is <code>null</code>,
   * no exception is thrown and no action is performed.
   *
   * @param           l the item listener
   * @see             #addItemListener
   * @see             java.awt.event.ItemEvent
   * @see             java.awt.event.ItemListener
   * @since           JDK1.1
   */
  public synchronized void removeItemListener( ItemListener l )
  {
    if ( l == null ) {
      return;
    }
    itemListener = AWTEventMulticaster.remove( itemListener, l );
  }

  /**
   * Returns the selected items on the list in an array of objects.
   * @see ItemSelectable
   * @return the selected object/row
   */
  public Object[] getSelectedObjects()
  {
    Integer sel[] = new Integer[ 1 ];
    sel[ 0 ] = new Integer( selectedRow );

    return sel;
  }

  /**
   * Check if the component has rendered yet.
   * @return true if rendered at least once
   */
  boolean hasRendered()
  {
    return rendered;
  }

  transient ItemListener itemListener;
}
TOP

Related Classes of net.xoetrope.awt.XTableRenderer

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.