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