001: /*
002: * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
003: *
004: * http://izpack.org/
005: * http://izpack.codehaus.org/
006: *
007: * Copyright 1997,2002 Elmar Grom
008: *
009: * Licensed under the Apache License, Version 2.0 (the "License");
010: * you may not use this file except in compliance with the License.
011: * You may obtain a copy of the License at
012: *
013: * http://www.apache.org/licenses/LICENSE-2.0
014: *
015: * Unless required by applicable law or agreed to in writing, software
016: * distributed under the License is distributed on an "AS IS" BASIS,
017: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018: * See the License for the specific language governing permissions and
019: * limitations under the License.
020: */
021:
022: package com.izforge.izpack.util;
023:
024: import java.awt.Color;
025: import java.awt.Dimension;
026: import java.awt.Font;
027: import java.awt.FontMetrics;
028: import java.awt.Graphics;
029: import java.util.Vector;
030:
031: import javax.swing.JComponent;
032:
033: /*---------------------------------------------------------------------------*/
034: /**
035: * <BR>
036: * <code>MultiLineLabel</code> may be used in place of javax.swing.JLabel. <BR>
037: * <BR>
038: * This class implements a component that is capable of displaying multiple lines of text. Line
039: * breaks are inserted automatically whenever a line of text extends beyond the predefined maximum
040: * line length. Line breaks will only be inserted between words, except where a single word is
041: * longer than the maximum line length. Line breaks may be forced at any location in the text by
042: * inserting a newline (\n). White space that is not valuable (i.e. is placed at the beginning of a
043: * new line or at the very beginning or end of the text) is removed. <br>
044: * <br>
045: * <b>Note:</b> you can set the maximum width of the label either through one of the constructors
046: * or you can call <code>setMaxWidth()</code> explicitly. If this is not set,
047: * <code>MultiLineLabel</code> will derive its width from the parent component.
048: *
049: * @version 0.0.1 / 05-15-97
050: * @version 1.0 / 04-13-02
051: * @author Elmar Grom
052: */
053: /*---------------------------------------------------------------------------*
054: * Reviving some old code here that was written before there was swing.
055: * The original was written to work with awt. I had to do some masaging to
056: * make it a JComponent and I hope it behaves like a reasonably good mannered
057: * swing component.
058: *---------------------------------------------------------------------------*/
059: public class MultiLineLabel extends JComponent {
060:
061: /**
062: *
063: */
064: private static final long serialVersionUID = 4051045255031894837L;
065:
066: public static final int LEFT = 0; // alignment constants
067:
068: public static final int CENTER = 1;
069:
070: public static final int RIGHT = 2;
071:
072: public static final int DEFAULT_MARGIN = 10;
073:
074: public static final int DEFAULT_ALIGN = LEFT;
075:
076: public static final int LEAST_ALLOWED = 200; // default setting for
077:
078: // maxAllowed
079:
080: private static final int FOUND = 0; // constants for string search.
081:
082: private static final int NOT_FOUND = 1;
083:
084: private static final int NOT_DONE = 0;
085:
086: private static final int DONE = 1;
087:
088: private static final char[] WHITE_SPACE = { ' ', '\n', '\t' };
089:
090: private static final char[] SPACES = { ' ', '\t' };
091:
092: private static final char NEW_LINE = '\n';
093:
094: protected Vector<String> line = new Vector<String>();// text lines to display
095:
096: protected String labelText; // text lines to display
097:
098: protected int numLines; // the number of lines
099:
100: protected int marginHeight; // top and bottom margins
101:
102: protected int marginWidth; // left and right margins
103:
104: protected int lineHeight; // total height of the font
105:
106: protected int lineAscent; // font height above the baseline
107:
108: protected int lineDescent; // font hight below the baseline
109:
110: protected int[] lineWidth; // width of each line
111:
112: protected int maxWidth; // width of the widest line
113:
114: private int maxAllowed = LEAST_ALLOWED; // max width allowed to use
115:
116: private boolean maxAllowedSet = false; // signals if the max allowed width
117:
118: // has been explicitly set
119:
120: protected int alignment = LEFT; // default text alignment
121:
122: /*-------------------------------------------------------------------*/
123: /**
124: * Constructor
125: *
126: * @param text the text to be displayed
127: * @param horMargin the horizontal margin for the label
128: * @param vertMargin the vertical margin for the label
129: * @param maxWidth the maximum allowed width of the text
130: * @param justify the text alignment for the label
131: */
132: /*-------------------------------------------------------------------*
133: * <detailed description / implementation details if applicable>
134: *-------------------------------------------------------------------*/
135: public MultiLineLabel(String text, int horMargin, int vertMargin,
136: int maxWidth, int justify) {
137: this .labelText = text;
138: this .marginWidth = horMargin;
139: this .marginHeight = vertMargin;
140: this .maxAllowed = maxWidth;
141: this .maxAllowedSet = true;
142: this .alignment = justify;
143: }
144:
145: /*-------------------------------------------------------------------*/
146: /**
147: * Constructor using default max-width and alignment.
148: *
149: * @param label the text to be displayed
150: * @param marginWidth the horizontal margin for the label
151: * @param marginHeight the vertical margin for the label
152: */
153: /*-------------------------------------------------------------------*
154: * <detailed description / implementation details if applicable>
155: *-------------------------------------------------------------------*/
156: public MultiLineLabel(String label, int marginWidth,
157: int marginHeight) {
158: this .labelText = label;
159: this .marginWidth = marginWidth;
160: this .marginHeight = marginHeight;
161: }
162:
163: /*-------------------------------------------------------------------*/
164: /**
165: * Constructor using default max-width, and margin.
166: *
167: * @param label the text to be displayed
168: * @param alignment the text alignment for the label
169: */
170: /*-------------------------------------------------------------------*
171: * <detailed description / implementation details if applicable>
172: *-------------------------------------------------------------------*/
173: public MultiLineLabel(String label, int alignment) {
174: this .labelText = label;
175: this .alignment = alignment;
176: }
177:
178: /*-------------------------------------------------------------------*/
179: /**
180: * Constructor using default max-width, alignment, and margin.
181: *
182: * @param label the text to be displayed
183: */
184: /*-------------------------------------------------------------------*
185: * <detailed description / implementation details if applicable>
186: *-------------------------------------------------------------------*/
187: public MultiLineLabel(String label) {
188: this .labelText = label;
189: }
190:
191: /*-------------------------------------------------------------------*/
192: /**
193: * This method searches the target string for occurences of any of the characters in the source
194: * string. The return value is the position of the first hit. Based on the mode parameter the
195: * hit position is either the position where any of the source characters first was found or the
196: * first position where none of the source characters where found.
197: *
198: *
199: * @return position of the first occurence
200: * @param target the text to be searched
201: * @param start the start position for the search
202: * @param source the list of characters to be searched for
203: * @param mode the search mode FOUND = reports first found NOT_FOUND = reports first not found
204: */
205: /*-------------------------------------------------------------------*
206: * <detailed description / implementation details if applicable>
207: *-------------------------------------------------------------------*/
208: int getPosition(String target, int start, char[] source, int mode) {
209: int status;
210: int position;
211: int scan;
212: int targetEnd;
213: int sourceLength;
214: char temp;
215:
216: targetEnd = (target.length() - 1);
217: sourceLength = source.length;
218: position = start;
219:
220: if (mode == FOUND) {
221: status = NOT_DONE;
222: while (status != DONE) {
223: position++;
224: if (!(position < targetEnd)) // end of string reached, the
225: // next
226: { // statement would cause a runtime error
227: return (targetEnd);
228: }
229: temp = target.charAt(position);
230: for (scan = 0; scan < sourceLength; scan++) // walk through the
231: // source
232: { // string and compare each char
233: if (source[scan] == temp) {
234: status = DONE;
235: }
236: }
237: }
238: return (position);
239: } else if (mode == NOT_FOUND) {
240: status = NOT_DONE;
241: while (status != DONE) {
242: position++;
243: if (!(position < targetEnd)) // end of string reached, the
244: // next
245: { // statement would cause a runtime error
246: return (targetEnd);
247: }
248: temp = target.charAt(position);
249: status = DONE;
250: for (scan = 0; scan < sourceLength; scan++) // walk through the
251: // source
252: { // string and compare each char
253: if (source[scan] == temp) {
254: status = NOT_DONE;
255: }
256: }
257: }
258: return (position);
259: }
260: return (0);
261: }
262:
263: /*-------------------------------------------------------------------*/
264: /**
265: * This method scans the input string until the max allowed width is reached. The return value
266: * indicates the position just before this happens.
267: *
268: *
269: * @return position character position just before the string is too long
270: * @param word word to break
271: */
272: /*-------------------------------------------------------------------*
273: * <detailed description / implementation details if applicable>
274: *-------------------------------------------------------------------*/
275: int breakWord(String word, FontMetrics fm) {
276: int width;
277: int currentPos;
278: int endPos;
279:
280: width = 0;
281: currentPos = 0;
282: endPos = word.length() - 1;
283:
284: // make sure we don't end up with a negative position
285: if (endPos <= 0) {
286: return (currentPos);
287: }
288: // seek the position where the word first is longer than allowed
289: while ((width < maxAllowed) && (currentPos < endPos)) {
290: currentPos++;
291: width = fm.stringWidth(labelText.substring(0, currentPos));
292: }
293: // adjust to get the chatacter just before (this should make it a bit
294: // shorter than allowed!)
295: if (currentPos != endPos) {
296: currentPos--;
297: }
298: return (currentPos);
299: }
300:
301: /*-------------------------------------------------------------------*/
302: /**
303: * This method breaks the label text up into multiple lines of text. Line breaks are established
304: * based on the maximum available space. A new line is started whenever a line break is
305: * encountered, even if the permissible length is not yet reached. Words are broken only if a
306: * single word happens to be longer than one line.
307: */
308: /*-------------------------------------------------------------------*/
309: private void divideLabel() {
310: int width;
311: int startPos;
312: int currentPos;
313: int lastPos;
314: int endPos;
315:
316: line.clear();
317: FontMetrics fm = this .getFontMetrics(this .getFont());
318:
319: startPos = 0;
320: currentPos = startPos;
321: lastPos = currentPos;
322: endPos = (labelText.length() - 1);
323:
324: while (currentPos < endPos) {
325: width = 0;
326: // ----------------------------------------------------------------
327: // find the first substring that occupies more than the granted
328: // space.
329: // Break at the end of the string or a line break
330: // ----------------------------------------------------------------
331: while ((width < maxAllowed) && (currentPos < endPos)
332: && (labelText.charAt(currentPos) != NEW_LINE)) {
333: lastPos = currentPos;
334: currentPos = getPosition(labelText, currentPos,
335: WHITE_SPACE, FOUND);
336: width = fm.stringWidth(labelText.substring(startPos,
337: currentPos));
338: }
339: // ----------------------------------------------------------------
340: // if we have a line break we want to copy everything up to
341: // currentPos
342: // ----------------------------------------------------------------
343: if (labelText.charAt(currentPos) == NEW_LINE) {
344: lastPos = currentPos;
345: }
346: // ----------------------------------------------------------------
347: // if we are at the end of the string we want to copy everything up
348: // to
349: // the last character. Since there seems to be a problem to get the
350: // last
351: // character if the substring definition ends at the very last
352: // character
353: // we have to call a different substring function than normal.
354: // ----------------------------------------------------------------
355: if (currentPos == endPos && width <= maxAllowed) {
356: lastPos = currentPos;
357: String s = labelText.substring(startPos);
358: line.addElement(s);
359: }
360: // ----------------------------------------------------------------
361: // in all other cases copy the substring that we have found to fit
362: // and
363: // add it as a new line of text to the line vector.
364: // ----------------------------------------------------------------
365: else {
366: // ------------------------------------------------------------
367: // make sure it's not a single word. If so we must break it at
368: // the
369: // proper location.
370: // ------------------------------------------------------------
371: if (lastPos == startPos) {
372: lastPos = startPos
373: + breakWord(labelText.substring(startPos,
374: currentPos), fm);
375: }
376: String s = labelText.substring(startPos, lastPos);
377: line.addElement(s);
378: }
379:
380: // ----------------------------------------------------------------
381: // seek for the end of the white space to cut out any unnecessary
382: // spaces
383: // and tabs and set the new start condition.
384: // ----------------------------------------------------------------
385: startPos = getPosition(labelText, lastPos, SPACES,
386: NOT_FOUND);
387: currentPos = startPos;
388: }
389:
390: numLines = line.size();
391: lineWidth = new int[numLines];
392: }
393:
394: /*-------------------------------------------------------------------*/
395: /**
396: * This method finds the font size, each line width and the widest line.
397: *
398: */
399: /*-------------------------------------------------------------------*/
400: protected void measure() {
401: if (!maxAllowedSet) {
402: maxAllowed = getParent().getSize().width;
403: }
404:
405: // return if width is too small
406: if (maxAllowed < (20)) {
407: return;
408: }
409:
410: FontMetrics fm = this .getFontMetrics(this .getFont());
411:
412: // return if no font metrics available
413: if (fm == null) {
414: return;
415: }
416:
417: divideLabel();
418:
419: this .lineHeight = fm.getHeight();
420: this .lineDescent = fm.getDescent();
421: this .maxWidth = 0;
422:
423: for (int i = 0; i < numLines; i++) {
424: this .lineWidth[i] = fm.stringWidth(this .line.elementAt(i));
425: if (this .lineWidth[i] > this .maxWidth) {
426: this .maxWidth = this .lineWidth[i];
427: }
428: }
429: }
430:
431: /*-------------------------------------------------------------------*/
432: /**
433: * This method draws the label.
434: *
435: * @param graphics the device context
436: */
437: /*-------------------------------------------------------------------*/
438: public void paint(Graphics graphics) {
439: int x;
440: int y;
441:
442: measure();
443: Dimension d = this .getSize();
444:
445: y = lineAscent + (d.height - (numLines * lineHeight)) / 2;
446:
447: for (int i = 0; i < numLines; i++) {
448: y += lineHeight;
449: switch (alignment) {
450: case LEFT:
451: x = marginWidth;
452: break;
453: case CENTER:
454: x = (d.width - lineWidth[i]) / 2;
455: break;
456: case RIGHT:
457: x = d.width - marginWidth - lineWidth[i];
458: break;
459: default:
460: x = (d.width - lineWidth[i]) / 2;
461: }
462: graphics.drawString(line.elementAt(i), x, y);
463: }
464: }
465:
466: /*-------------------------------------------------------------------*/
467: /**
468: * This method may be used to set the label text
469: *
470: * @param labelText the text to be displayed
471: */
472: /*-------------------------------------------------------------------*/
473: public void setText(String labelText) {
474: this .labelText = labelText;
475: repaint();
476: }
477:
478: /*-------------------------------------------------------------------*/
479: /**
480: * This method may be used to set the font that should be used to draw the label
481: *
482: * @param font font to be used within the label
483: */
484: /*-------------------------------------------------------------------*/
485: public void setFont(Font font) {
486: super .setFont(font);
487: repaint();
488: }
489:
490: /*-------------------------------------------------------------------*/
491: /**
492: * This method may be used to set the color in which the text should be drawn
493: *
494: * @param color the text color
495: */
496: /*-------------------------------------------------------------------*/
497: public void setColor(Color color) {
498: super .setForeground(color);
499: repaint();
500: }
501:
502: /*-------------------------------------------------------------------*/
503: /**
504: * This method may be used to set the text alignment for the label
505: *
506: * @param alignment the alignment, possible values are LEFT, CENTER, RIGHT
507: */
508: /*-------------------------------------------------------------------*/
509: public void setJustify(int alignment) {
510: this .alignment = alignment;
511: repaint();
512: }
513:
514: /*-------------------------------------------------------------------*/
515: /**
516: * This method may be used to set the max allowed line width
517: *
518: * @param width the max allowed line width in pixels
519: */
520: /*-------------------------------------------------------------------*/
521: public void setMaxWidth(int width) {
522: this .maxAllowed = width;
523: this .maxAllowedSet = true;
524: repaint();
525: }
526:
527: /*-------------------------------------------------------------------*/
528: /**
529: * This method may be used to set the horizontal margin
530: *
531: * @param margin the margin to the left and to the right of the label
532: */
533: /*-------------------------------------------------------------------*/
534: public void setMarginWidth(int margin) {
535: this .marginWidth = margin;
536: repaint();
537: }
538:
539: /*-------------------------------------------------------------------*/
540: /**
541: * This method may be used to set the vertical margin for the label
542: *
543: * @param margin the margin on the top and bottom of the label
544: */
545: /*-------------------------------------------------------------------*/
546: public void setMarginHeight(int margin) {
547: this .marginHeight = margin;
548: repaint();
549: }
550:
551: /*-------------------------------------------------------------------*/
552: /**
553: * Moves and resizes this component. The new location of the top-left corner is specified by
554: * <code>x</code> and <code>y</code>, and the new size is specified by <code>width</code>
555: * and <code>height</code>.
556: *
557: * @param x The new x-coordinate of this component.
558: * @param y The new y-coordinate of this component.
559: * @param width The new width of this component.
560: * @param height The new height of this component.
561: */
562: /*-------------------------------------------------------------------*/
563: public void setBounds(int x, int y, int width, int height) {
564: super .setBounds(x, y, width, height);
565: this .maxAllowed = width;
566: this .maxAllowedSet = true;
567: }
568:
569: /*-------------------------------------------------------------------*/
570: /**
571: * This method may be used to retrieve the text alignment for the label
572: *
573: * @return alignment the text alignment currently in use for the label
574: */
575: /*-------------------------------------------------------------------*/
576: public int getAlignment() {
577: return (this .alignment);
578: }
579:
580: /*-------------------------------------------------------------------*/
581: /**
582: * This method may be used to retrieve the horizontal margin for the label
583: *
584: * @return marginWidth the margin currently in use to the left and right of the label
585: */
586: /*-------------------------------------------------------------------*/
587: public int getMarginWidth() {
588: return (this .marginWidth);
589: }
590:
591: /*-------------------------------------------------------------------*/
592: /**
593: * This method may be used to retrieve the vertical margin for the label
594: *
595: * @return marginHeight the margin currently in use on the top and bottom of the label
596: */
597: /*-------------------------------------------------------------------*/
598: public int getMarginHeight() {
599: return (this .marginHeight);
600: }
601:
602: /*-------------------------------------------------------------------*/
603: /**
604: * This method is typically used by the layout manager, it reports the necessary space to
605: * display the label comfortably.
606: */
607: /*-------------------------------------------------------------------*/
608: public Dimension getPreferredSize() {
609: measure();
610: return (new Dimension(maxAllowed, (numLines * (lineHeight
611: + lineAscent + lineDescent))
612: + (2 * marginHeight)));
613: }
614:
615: /*-------------------------------------------------------------------*/
616: /**
617: * This method is typically used by the layout manager, it reports the absolute minimum space
618: * required to display the entire label.
619: *
620: */
621: /*-------------------------------------------------------------------*/
622: public Dimension getMinimumSize() {
623: measure();
624: return (new Dimension(maxAllowed, (numLines * (lineHeight
625: + lineAscent + lineDescent))
626: + (2 * marginHeight)));
627: }
628:
629: /*-------------------------------------------------------------------*/
630: /**
631: * This method is called by the system after this object is first created.
632: *
633: */
634: /*-------------------------------------------------------------------*/
635: public void addNotify() {
636: super .addNotify(); // invoke the superclass
637: }
638: }
639: /*---------------------------------------------------------------------------*/
|