001 /* ParagraphView.java -- A composite View 002 Copyright (C) 2005 Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package javax.swing.text; 040 041 import java.awt.Color; 042 import java.awt.Graphics; 043 import java.awt.Rectangle; 044 import java.awt.Shape; 045 046 import javax.swing.SizeRequirements; 047 import javax.swing.event.DocumentEvent; 048 049 /** 050 * A {@link FlowView} that flows it's children horizontally and boxes the rows 051 * vertically. 052 * 053 * @author Roman Kennke (roman@kennke.org) 054 */ 055 public class ParagraphView extends FlowView implements TabExpander 056 { 057 /** 058 * A specialized horizontal <code>BoxView</code> that represents exactly 059 * one row in a <code>ParagraphView</code>. 060 */ 061 class Row extends BoxView 062 { 063 /** 064 * Creates a new instance of <code>Row</code>. 065 */ 066 Row(Element el) 067 { 068 super(el, X_AXIS); 069 } 070 071 /** 072 * Overridden to adjust when we are the first line, and firstLineIndent 073 * is not 0. 074 */ 075 public short getLeftInset() 076 { 077 short leftInset = super.getLeftInset(); 078 View parent = getParent(); 079 if (parent != null) 080 { 081 if (parent.getView(0) == this) 082 leftInset += firstLineIndent; 083 } 084 return leftInset; 085 } 086 087 public float getAlignment(int axis) 088 { 089 float align; 090 if (axis == X_AXIS) 091 switch (justification) 092 { 093 case StyleConstants.ALIGN_RIGHT: 094 align = 1.0F; 095 break; 096 case StyleConstants.ALIGN_CENTER: 097 case StyleConstants.ALIGN_JUSTIFIED: 098 align = 0.5F; 099 break; 100 case StyleConstants.ALIGN_LEFT: 101 default: 102 align = 0.0F; 103 } 104 else 105 align = super.getAlignment(axis); 106 return align; 107 } 108 109 /** 110 * Overridden because child views are not necessarily laid out in model 111 * order. 112 */ 113 protected int getViewIndexAtPosition(int pos) 114 { 115 int index = -1; 116 if (pos >= getStartOffset() && pos < getEndOffset()) 117 { 118 int nviews = getViewCount(); 119 for (int i = 0; i < nviews && index == -1; i++) 120 { 121 View child = getView(i); 122 if (pos >= child.getStartOffset() && pos < child.getEndOffset()) 123 index = i; 124 } 125 } 126 return index; 127 } 128 129 130 /** 131 * Overridden to perform a baseline layout. The normal BoxView layout 132 * isn't completely suitable for rows. 133 */ 134 protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, 135 int[] spans) 136 { 137 baselineLayout(targetSpan, axis, offsets, spans); 138 } 139 140 /** 141 * Overridden to perform a baseline layout. The normal BoxView layout 142 * isn't completely suitable for rows. 143 */ 144 protected SizeRequirements calculateMinorAxisRequirements(int axis, 145 SizeRequirements r) 146 { 147 return baselineRequirements(axis, r); 148 } 149 150 protected void loadChildren(ViewFactory vf) 151 { 152 // Do nothing here. The children are added while layouting. 153 } 154 155 /** 156 * Overridden to determine the minimum start offset of the row's children. 157 */ 158 public int getStartOffset() 159 { 160 // Determine minimum start offset of the children. 161 int offset = Integer.MAX_VALUE; 162 int n = getViewCount(); 163 for (int i = 0; i < n; i++) 164 { 165 View v = getView(i); 166 offset = Math.min(offset, v.getStartOffset()); 167 } 168 return offset; 169 } 170 171 /** 172 * Overridden to determine the maximum end offset of the row's children. 173 */ 174 public int getEndOffset() 175 { 176 // Determine minimum start offset of the children. 177 int offset = 0; 178 int n = getViewCount(); 179 for (int i = 0; i < n; i++) 180 { 181 View v = getView(i); 182 offset = Math.max(offset, v.getEndOffset()); 183 } 184 return offset; 185 } 186 } 187 188 /** 189 * The indentation of the first line of the paragraph. 190 */ 191 protected int firstLineIndent; 192 193 /** 194 * The justification of the paragraph. 195 */ 196 private int justification; 197 198 /** 199 * The line spacing of this paragraph. 200 */ 201 private float lineSpacing; 202 203 /** 204 * The TabSet of this paragraph. 205 */ 206 private TabSet tabSet; 207 208 /** 209 * Creates a new <code>ParagraphView</code> for the given 210 * <code>Element</code>. 211 * 212 * @param element the element that is rendered by this ParagraphView 213 */ 214 public ParagraphView(Element element) 215 { 216 super(element, Y_AXIS); 217 } 218 219 public float nextTabStop(float x, int tabOffset) 220 { 221 throw new InternalError("Not implemented yet"); 222 } 223 224 /** 225 * Creates a new view that represents a row within a flow. 226 * 227 * @return a view for a new row 228 */ 229 protected View createRow() 230 { 231 return new Row(getElement()); 232 } 233 234 /** 235 * Returns the alignment for this paragraph view for the specified axis. 236 * For the X_AXIS the paragraph view will be aligned at it's left edge 237 * (0.0F). For the Y_AXIS the paragraph view will be aligned at the 238 * center of it's first row. 239 * 240 * @param axis the axis which is examined 241 * 242 * @return the alignment for this paragraph view for the specified axis 243 */ 244 public float getAlignment(int axis) 245 { 246 float align; 247 if (axis == X_AXIS) 248 align = 0.5F; 249 else if (getViewCount() > 0) 250 { 251 float prefHeight = getPreferredSpan(Y_AXIS); 252 float firstRowHeight = getView(0).getPreferredSpan(Y_AXIS); 253 align = (firstRowHeight / 2.F) / prefHeight; 254 } 255 else 256 align = 0.5F; 257 return align; 258 } 259 260 /** 261 * Receives notification when some attributes of the displayed element 262 * changes. This triggers a refresh of the cached attributes of this 263 * paragraph. 264 * 265 * @param ev the document event 266 * @param a the allocation of this view 267 * @param vf the view factory to use for creating new child views 268 */ 269 public void changedUpdate(DocumentEvent ev, Shape a, ViewFactory vf) 270 { 271 setPropertiesFromAttributes(); 272 layoutChanged(X_AXIS); 273 layoutChanged(Y_AXIS); 274 super.changedUpdate(ev, a, vf); 275 } 276 277 /** 278 * Fetches the cached properties from the element's attributes. 279 */ 280 protected void setPropertiesFromAttributes() 281 { 282 Element el = getElement(); 283 AttributeSet atts = el.getAttributes(); 284 setFirstLineIndent(StyleConstants.getFirstLineIndent(atts)); 285 setLineSpacing(StyleConstants.getLineSpacing(atts)); 286 setJustification(StyleConstants.getAlignment(atts)); 287 tabSet = StyleConstants.getTabSet(atts); 288 } 289 290 /** 291 * Sets the indentation of the first line of the paragraph. 292 * 293 * @param i the indentation to set 294 */ 295 protected void setFirstLineIndent(float i) 296 { 297 firstLineIndent = (int) i; 298 } 299 300 /** 301 * Sets the justification of the paragraph. 302 * 303 * @param j the justification to set 304 */ 305 protected void setJustification(int j) 306 { 307 justification = j; 308 } 309 310 /** 311 * Sets the line spacing for this paragraph. 312 * 313 * @param s the line spacing to set 314 */ 315 protected void setLineSpacing(float s) 316 { 317 lineSpacing = s; 318 } 319 320 /** 321 * Returns the i-th view from the logical views, before breaking into rows. 322 * 323 * @param i the index of the logical view to return 324 * 325 * @return the i-th view from the logical views, before breaking into rows 326 */ 327 protected View getLayoutView(int i) 328 { 329 return layoutPool.getView(i); 330 } 331 332 /** 333 * Returns the number of logical child views. 334 * 335 * @return the number of logical child views 336 */ 337 protected int getLayoutViewCount() 338 { 339 return layoutPool.getViewCount(); 340 } 341 342 /** 343 * Returns the TabSet used by this ParagraphView. 344 * 345 * @return the TabSet used by this ParagraphView 346 */ 347 protected TabSet getTabSet() 348 { 349 return tabSet; 350 } 351 352 /** 353 * Finds the next offset in the document that has one of the characters 354 * specified in <code>string</code>. If there is no such character found, 355 * this returns -1. 356 * 357 * @param string the characters to search for 358 * @param start the start offset 359 * 360 * @return the next offset in the document that has one of the characters 361 * specified in <code>string</code> 362 */ 363 protected int findOffsetToCharactersInString(char[] string, int start) 364 { 365 int offset = -1; 366 Document doc = getDocument(); 367 Segment text = new Segment(); 368 try 369 { 370 doc.getText(start, doc.getLength() - start, text); 371 int index = start; 372 373 searchLoop: 374 while (true) 375 { 376 char ch = text.next(); 377 if (ch == Segment.DONE) 378 break; 379 380 for (int j = 0; j < string.length; ++j) 381 { 382 if (string[j] == ch) 383 { 384 offset = index; 385 break searchLoop; 386 } 387 } 388 index++; 389 } 390 } 391 catch (BadLocationException ex) 392 { 393 // Ignore this and return -1. 394 } 395 return offset; 396 } 397 398 protected int getClosestPositionTo(int pos, Position.Bias bias, Shape a, 399 int direction, Position.Bias[] biasRet, 400 int rowIndex, int x) 401 throws BadLocationException 402 { 403 // FIXME: Implement this properly. However, this looks like it might 404 // have been replaced by viewToModel. 405 return pos; 406 } 407 408 /** 409 * Returns the size that is used by this view (or it's child views) between 410 * <code>startOffset</code> and <code>endOffset</code>. If the child views 411 * implement the {@link TabableView} interface, then this is used to 412 * determine the span, otherwise we use the preferred span of the child 413 * views. 414 * 415 * @param startOffset the start offset 416 * @param endOffset the end offset 417 * 418 * @return the span used by the view between <code>startOffset</code> and 419 * <code>endOffset</cod> 420 */ 421 protected float getPartialSize(int startOffset, int endOffset) 422 { 423 int startIndex = getViewIndex(startOffset, Position.Bias.Backward); 424 int endIndex = getViewIndex(endOffset, Position.Bias.Forward); 425 float span; 426 if (startIndex == endIndex) 427 { 428 View child = getView(startIndex); 429 if (child instanceof TabableView) 430 { 431 TabableView tabable = (TabableView) child; 432 span = tabable.getPartialSpan(startOffset, endOffset); 433 } 434 else 435 span = child.getPreferredSpan(X_AXIS); 436 } 437 else if (endIndex - startIndex == 1) 438 { 439 View child1 = getView(startIndex); 440 if (child1 instanceof TabableView) 441 { 442 TabableView tabable = (TabableView) child1; 443 span = tabable.getPartialSpan(startOffset, child1.getEndOffset()); 444 } 445 else 446 span = child1.getPreferredSpan(X_AXIS); 447 View child2 = getView(endIndex); 448 if (child2 instanceof TabableView) 449 { 450 TabableView tabable = (TabableView) child2; 451 span += tabable.getPartialSpan(child2.getStartOffset(), endOffset); 452 } 453 else 454 span += child2.getPreferredSpan(X_AXIS); 455 } 456 else 457 { 458 // Start with the first view. 459 View child1 = getView(startIndex); 460 if (child1 instanceof TabableView) 461 { 462 TabableView tabable = (TabableView) child1; 463 span = tabable.getPartialSpan(startOffset, child1.getEndOffset()); 464 } 465 else 466 span = child1.getPreferredSpan(X_AXIS); 467 468 // Add up the view spans between the start and the end view. 469 for (int i = startIndex + 1; i < endIndex; i++) 470 { 471 View child = getView(i); 472 span += child.getPreferredSpan(X_AXIS); 473 } 474 475 // Add the span of the last view. 476 View child2 = getView(endIndex); 477 if (child2 instanceof TabableView) 478 { 479 TabableView tabable = (TabableView) child2; 480 span += tabable.getPartialSpan(child2.getStartOffset(), endOffset); 481 } 482 else 483 span += child2.getPreferredSpan(X_AXIS); 484 } 485 return span; 486 } 487 488 /** 489 * Returns the location where the tabs are calculated from. This returns 490 * <code>0.0F</code> by default. 491 * 492 * @return the location where the tabs are calculated from 493 */ 494 protected float getTabBase() 495 { 496 return 0.0F; 497 } 498 499 /** 500 * @specnote This method is specified to take a Row parameter, which is a 501 * private inner class of that class, which makes it unusable from 502 * application code. Also, this method seems to be replaced by 503 * {@link FlowStrategy#adjustRow(FlowView, int, int, int)}. 504 * 505 */ 506 protected void adjustRow(Row r, int desiredSpan, int x) 507 { 508 } 509 510 /** 511 * @specnote This method's signature differs from the one defined in 512 * {@link View} and is therefore never called. It is probably there 513 * for historical reasons. 514 */ 515 public View breakView(int axis, float len, Shape a) 516 { 517 // This method is not used. 518 return null; 519 } 520 521 /** 522 * @specnote This method's signature differs from the one defined in 523 * {@link View} and is therefore never called. It is probably there 524 * for historical reasons. 525 */ 526 public int getBreakWeight(int axis, float len) 527 { 528 // This method is not used. 529 return 0; 530 } 531 }