// Copyright 1996, Marimba Inc. All Rights Reserved.


// @(#)SparseTableContent.java, 1.18, 12/12/96





package marimba.gui;





import java.awt.*;


import java.util.*;





import marimba.persist.*;








/**


 * Class displaying the content of a sparse TableWidget.


 *


 * @see SparseTableWidget


 *


 * @author	Klaas Waslander


 * @version 	1.18, 12/12/96


 */


public class SparseTableContent extends TableContent {


    /**


     * Store the last view when layout() was executed.


     * In this way layout() can be smart when it is called


     * again: it only does a layout of the differences.


     * @see #lastData


     */


    protected int[]  lastView = {-1, -1, -1, -1};





    /**


     * Store the last visible data when layout() was executed.


     * @see #lastView


     */


    protected Vector[]  lastData = {new Vector()};





    /**


     * Store the last view when paint() was executed.


     * @see #lastPaintData


     */


    protected int[]  lastPaintView = {-1, -1, -1, -1};





    /**


     * Store the last visible data when paint() was executed.


     * @see #lastPaintView


     */


    protected Vector[]  lastPaintData = {new Vector()};








    /**


     * When scrolling the sparse table content, also do a


     * sparse layout.


     * @see #lastView


     */


    public void scroll(int tx, int ty) {


	super.scroll(tx, ty);


	layout();


    }





    /**


     * Flush the cache which stores the last data and view,


     * and remove all widgets from the content. After this the


     * content is back in its initial state; it contains nothing.


     */


    public void flush() {


	// flush layout cache


	int[]  newView = {-1, -1, -1, -1};


	lastView = newView;


	Vector[]  newData = {new Vector()};


	lastData = newData;





	// flush paint cache


	int[]  newPaintView = {-1, -1, -1, -1};


	lastPaintView = newPaintView;


	Vector[]  newPaintData = {new Vector()};


	lastPaintData = newPaintData;





	// remove all widgets from the content


	removeAll();


    }





    /**


     * Get data based for the specified old view, old data and new view.


     * It calls getData of the sparse table widget with an area as


     * small as possible and merges that with the old data.


     */


    protected Vector[] getData(int[] oldView, Vector[] oldData, int[] newView) {


	// determine overlap area of old and new view


	int  firstRow = Math.max(oldView[0], newView[0]);


	int  lastRow = Math.min(oldView[1], newView[1]);


	int  firstCol = Math.max(oldView[2], newView[2]);


	int  lastCol = Math.min(oldView[3], newView[3]);





	// return appropriate data


	SparseTableWidget  table = (SparseTableWidget)parent;


	Vector[]  result = null;


	if (firstRow > lastRow || firstCol > lastCol) {


	    result = table.getData(newView[0], newView[1], newView[2], newView[3]);


	} else {


	    result = new Vector[newView[1] - newView[0] + 1];





	    // initial resulting vector is overlap area


	    for (int rowIndex = firstRow; rowIndex <= lastRow; rowIndex++) {


		Vector  row = oldData[rowIndex-oldView[0]];


		Vector  newRow = new Vector();


		for (int colIndex = firstCol; colIndex <= lastCol; colIndex++) {


		    newRow.addElement(row.elementAt(colIndex - oldView[2]));


		}


		result[rowIndex-newView[0]] = newRow;


	    }





	    // insert new columns at left of overlap area


	    if (firstCol > newView[2]) {


		Vector[]  left = table.getData(firstRow, lastRow, newView[2], firstCol-1);


		for (int rowIndex = firstRow; rowIndex <= lastRow; rowIndex++) {


		    Vector  newRow = result[rowIndex-newView[0]];


		    Vector  leftRow = left[rowIndex-firstRow];


		    for (int colIndex = newView[2]; colIndex < firstCol; colIndex++) {


			Object  item = leftRow.elementAt(colIndex-newView[2]);


			newRow.insertElementAt(item, colIndex-newView[2]);


		    }


		}


	    }





	    // append new columns at right of overlap area


	    if (lastCol < newView[3]) {


		Vector[]  right = table.getData(firstRow, lastRow, lastCol+1, newView[3]);


		for (int rowIndex = firstRow; rowIndex <= lastRow; rowIndex++) {


		    Vector  newRow = result[rowIndex-newView[0]];


		    Vector  rightRow = right[rowIndex-firstRow];


		    for (int colIndex = lastCol+1; colIndex <= newView[3]; colIndex++) {


			Object  item = rightRow.elementAt(colIndex - lastCol - 1);


			newRow.addElement(item);


		    }


		}


	    }





	    // insert new rows at top of overlap area


	    if (firstRow > newView[0]) {


		Vector[]  top = table.getData(newView[0], firstRow-1, newView[2], newView[3]);


		for (int rowIndex = newView[0]; rowIndex < firstRow; rowIndex++) {


		    result[rowIndex - newView[0]] = top[rowIndex - newView[0]];


		}


	    }





	    // append new rows at bottom of overlap area


	    if (lastRow < newView[1]) {


		Vector[]  bottom = table.getData(lastRow+1, newView[1], newView[2], newView[3]);


		for (int rowIndex = lastRow+1; rowIndex <= newView[1]; rowIndex++) {


		    result[rowIndex - newView[0]] = bottom[rowIndex - lastRow - 1];


		}


	    }


	}


	return result;


    }





    /**


     * Layout the sparse table content, the widgets in the


     * content are being reshaped and added to show them


     * correctly.


     */


    public void layout() {


	SparseTableWidget  table = (SparseTableWidget)parent;


	int  rowPad = table.rowPadding;


	int  colPad = table.colPadding;





	// get current view with its data, return if nothing visible


	int[]  view = getView();


	if (view[1] == -1 || view[3] == -1) {


	    flush();


	    return;


	}





	// get the data, use the cached data if possible


	Vector[]  data;


	if (view[0] == lastView[0] && view[1] == lastView[1] &&


			view[2] == lastView[2] && view[3] == lastView[3]) {


	    data = lastData;


	} else if (view[0] == lastPaintView[0] && view[1] == lastPaintView[1] &&


			view[2] == lastPaintView[2] && view[3] == lastPaintView[3]) {


	    data = lastPaintData;


	} else {


	    data = getData(lastView, lastData, view);


	}





	// remove the widgets that were in the old view and are not in the new view


	for (int rowIndex = lastView[0]; rowIndex <= lastView[1]; rowIndex++) {


	    Vector  rowData = lastData[rowIndex-lastView[0]];





	    for (int colIndex = lastView[2]; colIndex <= lastView[3] && colIndex-lastView[2] < rowData.size(); colIndex++) {


		if (rowIndex < view[0] || rowIndex > view[1] ||


				colIndex < view[2] || colIndex > view[3]) {


		    Object  cellItem = rowData.elementAt(colIndex-lastView[2]);


		    if (cellItem instanceof Widget) {


			remove((Widget)cellItem);


		    }


		}


	    }


	}





	// layout the currently visible widgets that need layout


	int  yPos = view[0] * (table.rowHeight + rowPad * 2);


	for (int rowIndex = view[0]; rowIndex <= view[1]; rowIndex++) {


	    Vector  rowData = data[rowIndex-view[0]];





	    // reshape the visible widgets of this row


	    for (int colIndex = view[2]; colIndex <= view[3] && colIndex-view[2] < rowData.size(); colIndex++) {


		Object  cellItem = rowData.elementAt(colIndex-view[2]);





		if (cellItem instanceof Widget) {


		    TableColumn  col = table.getColumn(table.getColumnIndex(colIndex));


		    Widget  item = (Widget)cellItem;





		    // widgetheight and width adjusted if necessary


		    int  itemWidth;


		    if (table.sizeToCell) {


			itemWidth = col.size - colPad * 2;


		    } else {


			itemWidth = Math.min(item.width, col.size - colPad * 2);


			if (col.size >= 20 + colPad*2 && itemWidth < 20) {


			    itemWidth = 20;


			}


		    }





		    // add if cell item is a widget and when it was not in the last view


		    if (rowIndex < lastView[0] || rowIndex > lastView[1] ||


				    colIndex < lastView[2] || colIndex > lastView[3]) {


			add(item);


		    }





		    item.reshape(col.pos + colPad, yPos + rowPad, itemWidth, Math.min(table.rowHeight - rowPad * 2, item.height));


		}


	    }





	    // go to next row


	    yPos += table.rowHeight + rowPad * 2;


	}





	// store the view and data for the next layout


	lastView = view;


	lastData = data;





	// set the height of all the rows together


	rowsHeight = table.rowCount * (table.rowHeight + rowPad * 2);


    }





    /**


     * Get the current view: the currently visible rows and columns.


     * The integers returned represent the display index of each row and column.


     * It returns -1 for lastCol or lastRow when no columns or rows are visible.


     * @return an array with four integers: firstrow, lastrow, firstcol, lastcol.


     */


    public int[] getView() {


	SparseTableWidget  table = (SparseTableWidget)parent;


	int  rowPad = table.rowPadding;


	int  colPad = table.colPadding;





	// get the visible rows using the rowHeight


	int  firstRow = (-ty) / (table.rowHeight + rowPad * 2);


	int  lastRow = (-ty + height) / (table.rowHeight + rowPad * 2);


	lastRow = Math.min(table.rowCount-1, lastRow);





	// get the visible columns


	int  firstCol;


	for (firstCol = 0; firstCol < table.columnCount; firstCol++) {


	    TableColumn  col = table.getColumn(table.getColumnIndex(firstCol));


	    if (col.pos + col.size > (-tx)) {


		break;


	    }


	}


	int  lastCol;


	for (lastCol = firstCol; lastCol < table.columnCount; lastCol++) {


	    TableColumn  col = table.getColumn(table.getColumnIndex(lastCol));


	    if (col.pos + col.size > (-tx + width)) {


		break;


	    }


	}


	lastCol = Math.min(table.columnCount-1, lastCol);





	// return the results


	int  result[] = {firstRow, lastRow, firstCol, lastCol};


	return result;


    }





    /**


     * Paint the sparse table content.


     */


    public void paint(Graphics g) {


	paintColumnLines(g);





	// get current view with its data, return if nothing visible


	int[]  view = getView();


	if (view[1] == -1 || view[3] == -1) {


	    flush();


	    return;


	}


	SparseTableWidget  table = (SparseTableWidget)parent;





	// get the data, use the cached data if possible


	Vector[]  data;


	if (view[0] == lastView[0] && view[1] == lastView[1] &&


			view[2] == lastView[2] && view[3] == lastView[3]) {


	    data = lastData;


	} else if (view[0] == lastPaintView[0] && view[1] == lastPaintView[1] &&


			view[2] == lastPaintView[2] && view[3] == lastPaintView[3]) {


	    data = lastPaintData;


	} else {


	    data = getData(lastPaintView, lastPaintData, view);


	}





	// store the view and data for the next paint


	lastPaintView = view;


	lastPaintData = data;





	// create gc's for visible columns with correct clipping


	g.setColor(table.foreground);


	Graphics[]  colGC = new Graphics[view[3] - view[2] + 1];


	for (int colIndex = view[2]; colIndex <= view[3]; colIndex++) {


	    TableColumn  col = table.getColumn(table.getColumnIndex(colIndex));


	    Graphics  newGC = g.create();


	    newGC.clipRect(col.pos, -ty, col.size, (-ty)+height);


	    colGC[colIndex-view[2]] = newGC;


	}





	// paint the visible rows


	int  rowPad = table.rowPadding;


	int  colPad = table.colPadding;


	int  yPos = view[0] * (table.rowHeight + rowPad * 2);


	for (int rowIndex = view[0]; rowIndex <= view[1]; rowIndex++) {


	    Vector  rowData = data[rowIndex-view[0]];





	    // check whether this row is selected and paint


	    Color  foreColor;


	    if (table.rowIsSelectedAt(table.getRowIndex(rowIndex))) {


		if (!table.isDisabled()) {


		    g.setColor(getSelBackground());


		} else {


		    g.setColor(getForeground());


		}


		g.fillRect(-tx + 3, yPos, width-6, table.rowHeight + rowPad * 2);


		if (table.hasFocus() && table.getRowIndex(rowIndex) == table.currentRowIndex) {


		    g.setColor(getSelFocusColor());


		    Dash.drawFocusRect(g, -tx + 3, yPos, width-6, table.rowHeight + rowPad * 2);


		}


		foreColor = (!table.isDisabled()) ? getSelForeground() : getBackground();


	    } else {


		foreColor = foreground;


	    }





	    // paint the cells of this row that need repainting and are visible


	    FontMetrics  fm = g.getFontMetrics(font);


	    for (int colIndex = view[2]; colIndex <= view[3] && colIndex-view[2] < rowData.size(); colIndex++) {


		TableColumn  colInfo = table.getColumn(table.getColumnIndex(colIndex));


		Object  cellItem = rowData.elementAt(colIndex-view[2]);





		// draw necessary things, widgets draw themselves


		if (cellItem != null && !(cellItem instanceof Widget)) {


		    String  item = cellItem.toString();


		    colGC[colIndex-view[2]].setColor(foreColor);





		    int  strw = fm.stringWidth(item);


		    int  lx = colInfo.pos + colPad;


		    switch (colInfo.align) {


			case CENTER:


			    lx += (colInfo.size - colPad * 2 - strw) / 2;


			    break;


			case RIGHT:


			    lx += (colInfo.size - colPad * 2 - strw);


			    break;


		    }


		    colGC[colIndex-view[2]].drawString(item, lx, yPos + tableFontAscent() + rowPad);


		}


	    }





	    // go to next row


	    yPos += table.rowHeight + rowPad * 2;


	}





	// dispose the gc's for every column


	for (int colIndex = view[2]; colIndex <= view[3]; colIndex++) {


	    colGC[colIndex-view[2]].dispose();


	}


    }





    /**


     * Find the display index of the row in which


     * the event took place.


     * Returns -1 if no row is found.


     */


    protected int findRow(Event evt) {


	int  result = -1;


	SparseTableWidget  table = (SparseTableWidget)parent;


	int  rowPad = table.rowPadding;


	int  pos = 0;


	for (int rowIndex = 0; rowIndex < table.rowCount; rowIndex++) {


	    if (evt.y > pos && evt.y < pos + table.rowHeight + rowPad * 2) {


		result = rowIndex;


		break;


	    }


	    pos += (table.rowHeight + rowPad * 2);


	}


	return result;


    }


}


