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 }