/* tableCell.java A JDK 1.1 table Swing component. Created: 15 January 1999 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2010 The University of Texas at Austin Contact information Author Email: ganymede_author@arlut.utexas.edu Email mailing list: ganymede@arlut.utexas.edu US Mail: Computer Science Division Applied Research Laboratories The University of Texas at Austin PO Box 8029, Austin TX 78713-8029 Telephone: (512) 835-3200 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package arlut.csd.JTable; import java.awt.Font; import java.awt.FontMetrics; /*------------------------------------------------------------------------------ class tableCell ------------------------------------------------------------------------------*/ /** * * tableCell represents the contents of a single cell in the table. * * This class is responsible for the mechanics of word wrapping. * */ class tableCell { static final boolean debug = false; // -- /** * * The non-word-wrapped text for this cell * */ String origText; /** * * The word-wrapped text for this cell * */ String text; /** * * We may have a binary representation of the text displayed in this * cell recorded for convenience. * */ Object data = null; /** * * An individual cell may have a special font and colors set.. if so, * we'll track it with this variable. * */ tableAttr attr; /** * * Remember whether we are selected or not. * */ boolean selected; /** * * What column is this cell in? We use this not only to handle * column-specific attributes, but also to get a reference to our * baseTable when we need it for internal calculations. * */ tableCol col; int nominalWidth, // width before any wrapping currentWidth, // width of rightmost pixel of real text in this // cell, after wrapping lastOfficialWidth = 0; // what were we last wrapped to? /** * * How many rows has this cell been wrapped across? * */ private int rowSpan; /* -- */ public tableCell(tableCol col, String text, tableAttr attr) { this.col = col; this.attr = attr; this.selected = false; this.nominalWidth = 0; this.currentWidth = 0; this.setText(text); calcRowSpan(); } public tableCell(tableCol col, String text) { this(col, text, null); } public tableCell(tableCol col) { this(col, null, null); } /** * * This method reinitializes the cell to its virgin state. * */ public void clear() { this.selected = false; this.nominalWidth = 0; this.currentWidth = 0; this.setText(null); } /** * * This method sets the text for this cell. * */ public void setText(String newText) { origText = text = newText; if (origText != null) { currentWidth = nominalWidth = getMetrics().stringWidth(origText); if (lastOfficialWidth != 0) { this.wrap(lastOfficialWidth); } } else { currentWidth = nominalWidth = tableCanvas.mincolwidth; } calcRowSpan(); } /** * * This method is used to record a piece of random attendant * data with this cell. It is used by rowTable to facilitate * type-specific sorting. * */ public final void setData(Object data) { this.data = data; } /** * * This method retrieves a piece of random attendant * data held with this cell. It is used by rowTable to facilitate * type-specific sorting. * */ public final Object getData() { return data; } /** * * This method refreshes the cell's measurements, and should * be called after the fontmetrics for this cell have changed. * */ public void refresh() { currentWidth = nominalWidth = getMetrics().stringWidth(origText); if (lastOfficialWidth != 0) { this.wrap(lastOfficialWidth); } calcRowSpan(); } /** * * Return the fontmetrics that apply to this cell. * */ public FontMetrics getMetrics() { if (attr != null && attr.fontMetric !=null) { return attr.fontMetric; } else if (col.attr != null && col.attr.fontMetric != null) { return col.attr.fontMetric; } else { return col.rt.tableAttrib.fontMetric; } } /** * * Return this cell's font * */ public Font getFont() { if (attr != null && attr.fontMetric !=null) { return attr.font; } else if (col.attr != null && col.attr.font != null) { return col.attr.font; } else { return col.rt.tableAttrib.font; } } /** * * Return this cell's justification * */ public int getJust() { if (attr != null && attr.align != tableAttr.JUST_INHERIT) { return attr.align; } else if (col.attr != null && col.attr.align != tableAttr.JUST_INHERIT) { return col.attr.align; } else { return col.rt.tableAttrib.align; } } /** * * This method returns the width of this cell at its widest point, * after word wrapping has been performed. * */ public int getCurrentWidth() { return currentWidth; } /** * * This method returns the width that the cell's current text would * have if not wrapped. * */ public int getNominalWidth() { return nominalWidth; } /** * * This method returns the nth row of this * cell's text, where the first row is 0. * */ public String getText(int n) { if (text == null) { return null; } if (n+1 > rowSpan) { return ""; } else { int pos, oldpos = -1; for (int i = 0; i < n; i++) { pos = text.indexOf('\n', oldpos + 1); if (pos != -1) { oldpos = pos; } } if (text.indexOf('\n', oldpos+1) == -1) { return text.substring(oldpos+1); } else { return text.substring(oldpos+1, text.indexOf('\n', oldpos+1)); } } } /** * * This method wraps the contained text to a certain * number of pixels. * * @param wrap_length The width of the cell to wrap to, in pixels * */ public synchronized void wrap(int wrap_length) { char[] charAry; int p, p2, marker; StringBuilder result = new StringBuilder(); FontMetrics fm; /* -- */ // if we're empty, don't bother trying to wrap anything if (text == null) { return; } if (wrap_length < 5) { throw new IllegalArgumentException("bad params: wrap_length specified as " + wrap_length); } // if the adjustment is a small enough reduction that it won't affect our // line breaking, just return. Likewise, if we were already unwrapped // and our cell width just got bigger, we don't need to wrap. if (((wrap_length > currentWidth) && (wrap_length <= lastOfficialWidth)) || ((currentWidth == nominalWidth) && (wrap_length >= nominalWidth))) { return; } else { lastOfficialWidth = wrap_length; } fm = getMetrics(); if (debug) { System.err.println("String size = " + origText.length()); } this.currentWidth = 0; this.rowSpan = 1; // figure out what we want to do about wrapping the origText charAry = origText.toCharArray(); p = marker = 0; // each time through the loop, p starts out pointing to the same char as marker int localWidth; while (marker < charAry.length) { localWidth = 0; while ((p < charAry.length) && (charAry[p] != '\n') && (localWidth + fm.charWidth(charAry[p]) < wrap_length)) { localWidth += fm.charWidth(charAry[p++]); } // now p points to the character that terminated the loop.. either // the first character that extends past the desired wrap_length, // or the first newline after marker, or it will have overflowed // to be == charAry.length // remember what our current needs are after wrapping if (localWidth > this.currentWidth) { this.currentWidth = localWidth; } if (p == charAry.length) { if (debug) { System.err.println("At completion.."); } result.append(origText.substring(marker, p)); text = result.toString(); return; } if (debug) { System.err.println("Step 1: p = " + p + ", marker = " + marker); } if (charAry[p] == '\n') { /* We've got a newline. This newline is bound to have terminated the while loop above. Step p and marker past the newline and continue on with our loop. */ result.append(origText.substring(marker, p)); if (debug) { System.err.println("found natural newline.. current result = " + result.toString()); } p = marker = p+1; rowSpan++; continue; } if (debug) { System.err.println("Step 2: hit wrap length, back searching for whitespace break point"); } p2 = p; /* We've either hit the end of the string, or we've gotten past the wrap_length. Back p2 up to the last space before the wrap_length, if there is such a space. Note that if the next character in the string (the character immediately after the break point) is a space, we don't need to back up at all. We'll just print up to our current location, do the newline, and skip to the next line. */ if (p < charAry.length) { if (!isspace(charAry[p])) { /* back p2 up to the last white space before the break point */ while ((p2 > marker) && !isspace(charAry[p2])) { p2--; } } } // now we're guaranteed that p2 points to our break character, // or that p2 == marker, indicating no whitespace in this row // to split on /* If the line was completely filled (no place to break), we'll just copy the whole line out and force a break. */ if (p2 == marker) { p2 = p-1; if (debug) { System.err.println("Step 3: no opportunity for break, forcing.."); } } else { if (debug) { System.err.println("Step 3: found break at column " + p2); } } if (!isspace(charAry[p2])) { /* If weren't were able to back up to a space, copy out the whole line, including the break character (in this case, we'll be making the string one character longer by inserting a newline). */ if (debug) { System.err.println("appending: marker = " + marker + ", p2 = " + p2 + "+1"); } result.append(origText.substring(marker, p2+1)); } else { /* The break character is whitespace. We'll copy out the characters up to but not including the break character, which we will effectively replace with a newline. */ if (debug) { System.err.println("appending: marker = " + marker + ", p2 = " + p2); } result.append(origText.substring(marker, p2)); } /* If we have not reached the end of the string, newline */ if (p < charAry.length) { result.append("\n"); rowSpan++; } p = marker = p2 + 1; } text = result.toString(); } private boolean isspace(char c) { return (c == '\n' || c == ' ' || c == '\t'); } private void calcRowSpan() { if (text == null) { rowSpan = 1; return; } char[] cAry = text.toCharArray(); rowSpan = 1; for (int i = 0; i < cAry.length; i++) { if (cAry[i] == '\n') { rowSpan++; } } } /** * * This method returns the number of lines this cell desires * to occupy. * */ public int getRowSpan() { return rowSpan; } }