KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > swt > custom > WrappedContent


1 /*******************************************************************************
2  * Copyright (c) 2000, 2004 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.swt.custom;
12
13
14 import org.eclipse.swt.*;
15 import org.eclipse.swt.graphics.*;
16
17 /**
18  * An instance of class <code>WrappedContent</code> is used by
19  * StyledText to display wrapped lines. Lines are wrapped at word
20  * breaks which are marked by a space character. Trailing space
21  * behind words is kept on the current line.
22  * If the last remaining word on a line can not be fully displayed
23  * the line is wrapped character by character.
24  * WrappedContent wraps a StyledTextContent which provides the line
25  * data. The start offset and length of wrapped lines is calculated
26  * and updated based on recalculation requests and text changes.
27  * <p>
28  * All public methods in this class implement the
29  * <code>StyledTextContent</code> interface. Package visible
30  * methods are internal API for use by <code>StyledText</code>.
31  * </p>
32  */

33 class WrappedContent implements StyledTextContent {
34     final static int LINE_OFFSET = 0; // index of line offset in visualLines array
35
final static int LINE_LENGTH = 1; // index of line lenght in visualLines array
36

37     StyledTextRenderer renderer;
38     StyledTextContent logicalContent;
39     int[][] visualLines; // start and length of each visual line
40
int visualLineCount = 0;
41
42 /**
43  * Create a new instance.
44  *
45  * @param renderer <class>StyledTextRenderer</class> that renders
46  * the lines wrapped by the new instance.
47  * @param logicalContent StyledTextContent that provides the line
48  * data.
49  */

50 WrappedContent(StyledTextRenderer renderer, StyledTextContent logicalContent) {
51     this.renderer = renderer;
52     this.logicalContent = logicalContent;
53 }
54 /**
55  * @see StyledTextContent#addTextChangeListener(TextChangeListener)
56  */

57 public void addTextChangeListener(TextChangeListener listener) {
58     logicalContent.addTextChangeListener(listener);
59 }
60 /**
61  * Grow the lines array to at least the specified size.
62  * <p>
63  *
64  * @param numLines number of elements that the array should have
65  * at a minimum
66  */

67 private void ensureSize(int numLines) {
68     int size = visualLines.length;
69     if (size >= numLines) {
70         return;
71     }
72     int[][] newLines = new int[Math.max(size * 2, numLines)][2];
73     System.arraycopy(visualLines, 0, newLines, 0, size);
74     visualLines = newLines;
75     resetVisualLines(size, visualLines.length - size);
76 }
77 /**
78  * @see StyledTextContent#getCharCount()
79  */

80 public int getCharCount() {
81     return logicalContent.getCharCount();
82 }
83 /**
84  * @return the visual (wrapped) line at the specified index
85  * @see StyledTextContent#getLine(int)
86  */

87 public String JavaDoc getLine(int lineIndex) {
88     String JavaDoc line;
89     
90     // redirect call to logical content if there are no wrapped lines
91
if (visualLineCount == 0) {
92         line = logicalContent.getLine(lineIndex);
93     }
94     else {
95         if (lineIndex >= visualLineCount || lineIndex < 0) {
96             SWT.error(SWT.ERROR_INVALID_ARGUMENT);
97         }
98         line = logicalContent.getTextRange(visualLines[lineIndex][LINE_OFFSET], visualLines[lineIndex][LINE_LENGTH]);
99     }
100     return line;
101 }
102 /**
103  * Returns the visual (wrapped) line at given offset.
104  * <p>
105  * The offset is ambiguous if it identifies the end of a visual line and
106  * there is another visual line below. In this case the end of the visual
107  * line has the same offset as the beginning of the next visual line
108  * since the visual line break is not represented by any character in the
109  * logical line.
110  * In this ambiguous case the offset is assumed to represent the end of a
111  * visual line and the index of the first visual line is returned.
112  * </p>
113  *
114  * @param offset offset of the desired line.
115  * @return the index of the visual (wrapped) line at the specified offset
116  * @see StyledTextContent#getLineAtOffset(int)
117  */

118 public int getLineAtOffset(int offset) {
119     int lastLine = visualLineCount - 1;
120     int lastChar;
121
122     // redirect call to logical content if there are no wrapped lines
123
if (visualLineCount == 0) {
124         return logicalContent.getLineAtOffset(offset);
125     }
126     // can't use getCharCount to get the number of characters since this
127
// method is called in textChanged, when the logicalContent used by
128
// getCharCount has already changed. at that point the visual lines
129
// have not been updated yet and we thus need to use the old character
130
// count which is only available in the visual content.
131
lastChar = visualLines[lastLine][LINE_OFFSET] + visualLines[lastLine][LINE_LENGTH];
132     if (offset < 0 || (offset > 0 && offset > lastChar)) {
133         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
134     }
135     // if last line and the line is not empty you can ask for
136
// a position that doesn't exist (the one to the right of the
137
// last character) - for inserting
138
if (offset == lastChar) {
139         return lastLine;
140     }
141
142     int high = visualLineCount;
143     int low = -1;
144     int index = visualLineCount;
145     while (high - low > 1) {
146         index = (high + low) / 2;
147         int lineStart = visualLines[index][LINE_OFFSET];
148         if (offset >= lineStart) {
149             int lineEnd = lineStart + visualLines[index][LINE_LENGTH];
150             low = index;
151             if (offset <= lineEnd) {
152                 break;
153             }
154         }
155         else {
156             high = index;
157         }
158     }
159     if (low > 0 && offset == visualLines[low - 1][LINE_OFFSET] + visualLines[low - 1][LINE_LENGTH]) {
160         // end of a visual line/beginning of next visual line is ambiguous
161
// (they have the same offset). always return the first visual line
162
low--;
163     }
164     return low;
165 }
166 /**
167  * @return the number of visual (wrapped) lines
168  * @see StyledTextContent#getLineCount()
169  */

170 public int getLineCount() {
171     int lineCount = visualLineCount;
172     
173     // redirect call to logical content if there are no wrapped lines
174
if (visualLineCount == 0) {
175         lineCount = logicalContent.getLineCount();
176     }
177     return lineCount;
178 }
179 /**
180  * @see StyledTextContent#getLineDelimiter()
181  */

182 public String JavaDoc getLineDelimiter() {
183     return logicalContent.getLineDelimiter();
184 }
185 /**
186  * @return the start offset of the visual (wrapped) line at the given
187  * index
188  * @see StyledTextContent#getOffsetAtLine(int)
189  */

190 public int getOffsetAtLine(int lineIndex) {
191     int offset;
192     
193     // redirect call to logical content if there are no wrapped lines
194
if (visualLineCount == 0) {
195         offset = logicalContent.getOffsetAtLine(lineIndex);
196     }
197     else {
198         if (lineIndex >= visualLineCount || lineIndex < 0) {
199             SWT.error(SWT.ERROR_INVALID_ARGUMENT);
200         }
201         offset = visualLines[lineIndex][LINE_OFFSET];
202     }
203     return offset;
204 }
205 /**
206  * @see StyledTextContent#getTextRange(int, int)
207  */

208 public String JavaDoc getTextRange(int start, int length) {
209     return logicalContent.getTextRange(start, length);
210 }
211 /**
212  * Returns the number of visual (wrapped) lines.
213  *
214  * @return the number of visual (wrapped) lines
215  */

216 int getVisualLineCount() {
217     return visualLineCount;
218 }
219 /**
220  * @see StyledTextContent#removeTextChangeListener(TextChangeListener)
221  */

222 public void removeTextChangeListener(TextChangeListener listener) {
223     logicalContent.removeTextChangeListener(listener);
224 }
225 /**
226  * Reset the visual (wrapped) lines in the specified range.
227  * If the range specifies partial logical lines (e.g., startLine is
228  * the second of two visual lines) it is extended to reset all visual
229  * lines of a logical line.
230  * Following the reset the logical lines in the reset visual range are
231  * rewrapped.
232  * <p>
233  *
234  * @param startLine index of the first visual line
235  * @param lineCount number of visual lines
236  */

237 void reset(int startLine, int lineCount) {
238     if (lineCount <= 0 || visualLineCount == 0) {
239         return;
240     }
241     reset(startLine, lineCount, true);
242 }
243 /**
244  * Reset the visual (wrapped) lines in the specified range.
245  * If the range specifies partial logical lines (e.g., startLine is
246  * the second of two visual lines) it is extended to reset all visual
247  * lines of a logical line.
248  * <p>
249  *
250  * @param startLine index of the first visual line
251  * @param lineCount number of visual lines
252  * @param wrap true=rewrap the logical lines in the reset visual range
253  * false=don't rewrap lines. Visual lines will be left in an inconsistent
254  * state since there will be a range of unwrapped and unknown lines.
255  * @return the first visual line that was reset
256  */

257 private int reset(int startLine, int lineCount, boolean wrap) {
258     if (lineCount <= 0) {
259         return startLine;
260     }
261     // make sure that all visual lines of the first logical line are
262
// being reset. visualFirstLine is the first visual line of the
263
// first logical line that has at least one visual line reset.
264
int visualFirstLineOffset = getOffsetAtLine(startLine);
265     int logicalFirstLine = logicalContent.getLineAtOffset(visualFirstLineOffset);
266     int logicalFirstLineOffset = logicalContent.getOffsetAtLine(logicalFirstLine);
267     int visualFirstLine = getLineAtOffset(logicalFirstLineOffset);
268
269     lineCount += startLine - visualFirstLine;
270     startLine = visualFirstLine;
271         
272     // make sure that all visual lines of the last logical line are
273
// being reset.
274
int lastLine = startLine + lineCount - 1;
275     int lastLineEnd = visualLines[lastLine][LINE_OFFSET] + visualLines[lastLine][LINE_LENGTH];
276     int logicalEndLine = 0;
277         
278     while (lastLine < visualLineCount - 1 && lastLineEnd == visualLines[lastLine + 1][LINE_OFFSET]) {
279         lastLine++;
280         lastLineEnd = visualLines[lastLine][LINE_OFFSET] + visualLines[lastLine][LINE_LENGTH];
281     }
282     if (wrap) {
283         if (lastLine == visualLineCount - 1) {
284             logicalEndLine = logicalContent.getLineCount();
285         }
286         else {
287             logicalEndLine = logicalContent.getLineAtOffset(visualLines[lastLine + 1][LINE_OFFSET]);
288         }
289     }
290     lineCount = lastLine - startLine + 1;
291     resetVisualLines(startLine, lineCount);
292     visualLineCount -= lineCount;
293     if (wrap) {
294         // always recalculate line wrap immediately after a reset
295
// because the content always needs to be in a usable state.
296
// i.e., there must not be any reset but unwrapped lines
297
wrapLineRange(logicalFirstLine, logicalEndLine, startLine);
298     }
299     return startLine;
300 }
301 /**
302  * Reset the visual (wrapped) lines in the specified range.
303  * <p>
304  *
305  * @param startLine index of the first visual line
306  * @param lineCount number of visual lines
307  */

308 private void resetVisualLines(int startLine, int lineCount) {
309     int endLine = startLine + lineCount;
310
311     for (int i = startLine; i < endLine; i++) {
312         visualLines[i] = new int[] {-1, -1};
313     }
314 }
315 /**
316  * @see StyledTextContent#replaceTextRange(int, int, String)
317  */

318 public void replaceTextRange(int start, int replaceLength, String JavaDoc text) {
319     logicalContent.replaceTextRange(start, replaceLength, text);
320 }
321 /**
322  * @see StyledTextContent#setText(String)
323  */

324 public void setText(String JavaDoc text) {
325     logicalContent.setText(text);
326 }
327 /**
328  * Set the line wrap data for the specified visual (wrapped) line.
329  * <p>
330  *
331  * @param visualLineIndex index of the visual line
332  * @param visualLineOffset start offset of the visual line, relative
333  * to the start of the document
334  * @param visualLineLength length of the visual line
335  */

336 private void setVisualLine(int visualLineIndex, int visualLineOffset, int visualLineLength) {
337     ensureSize(visualLineCount + 1);
338     // is the space for the visual line already taken? can happen if
339
// there are more visual lines for a given logical line than before
340
if (visualLines[visualLineIndex][LINE_OFFSET] != -1) {
341         System.arraycopy(visualLines, visualLineIndex, visualLines, visualLineIndex + 1, visualLineCount - visualLineIndex);
342         visualLines[visualLineIndex] = new int[2];
343     }
344     visualLines[visualLineIndex][LINE_OFFSET] = visualLineOffset;
345     visualLines[visualLineIndex][LINE_LENGTH] = visualLineLength;
346     visualLineCount++;
347 }
348 /**
349  * Recalculates the line wrap for the lines affected by the
350  * text change.
351  * <p>
352  *
353  * @param startOffset the start offset of the text change
354  * @param newLineCount the number of inserted lines
355  * @param replaceLineCount the number of deleted lines
356  * @param newCharCount the number of new characters
357  * @param replaceCharCount the number of deleted characters
358  */

359 void textChanged(int startOffset, int newLineCount, int replaceLineCount, int newCharCount, int replaceCharCount) {
360     // do nothing if there are no wrapped lines
361
if (visualLineCount == 0) {
362         return;
363     }
364     int logicalStartLine = logicalContent.getLineAtOffset(startOffset);
365     int visualStartLine = getLineAtOffset(startOffset);
366     int visualReplaceLastLine = visualLineCount - 1;
367     int textChangeDelta = newCharCount - replaceCharCount;
368         
369     if (replaceLineCount > 0) {
370         visualReplaceLastLine = getLineAtOffset(startOffset + replaceCharCount);
371         // at the start of a visual line/end of the previous visual line?
372
if ((visualReplaceLastLine == 0 ||
373             visualLines[visualReplaceLastLine][LINE_OFFSET] == visualLines[visualReplaceLastLine - 1][LINE_OFFSET] + visualLines[visualReplaceLastLine - 1][LINE_LENGTH]) &&
374             visualReplaceLastLine != visualLineCount - 1) {
375             visualReplaceLastLine++;
376         }
377         visualStartLine = reset(visualStartLine, visualReplaceLastLine - visualStartLine + 1, false);
378     }
379     else {
380         visualStartLine = reset(visualStartLine, 1, false);
381     }
382     visualReplaceLastLine = wrapLineRange(logicalStartLine, logicalStartLine + 1 + newLineCount, visualStartLine);
383     for (int i = visualReplaceLastLine; i < visualLineCount; i++) {
384         visualLines[i][LINE_OFFSET] += textChangeDelta;
385     }
386 }
387 /**
388  * Wrap the logical lines in the given range at the current client
389  * area width of the StyledText widget
390  * <p>
391  *
392  * @param startLine first logical line to wrap
393  * @param endLine line after last logical line
394  * @param visualLineIndex visual (wrapped) line index that startLine
395  * corresponds to.
396  * @return index of the line following the last wrapped line
397  */

398 private int wrapLineRange(int startLine, int endLine, int visualLineIndex) {
399     int emptyLineCount = 0;
400         
401     int width = renderer.getClientArea().width - renderer.getLeftMargin() - renderer.getRightMargin();
402     visualLineIndex = wrapLineRange(startLine, endLine, visualLineIndex, width);
403     // is there space left for more visual lines? can happen if there are fewer
404
// visual lines for a given logical line than before
405
for (int i = visualLineIndex; i < visualLines.length; i++, emptyLineCount++) {
406         if (visualLines[i][LINE_OFFSET] != -1) {
407             break;
408         }
409     }
410     if (emptyLineCount > 0) {
411         int copyLineCount = visualLineCount - visualLineIndex;
412         System.arraycopy(visualLines, visualLineIndex + emptyLineCount, visualLines, visualLineIndex, copyLineCount);
413         resetVisualLines(visualLineIndex + copyLineCount, emptyLineCount);
414     }
415     return visualLineIndex;
416 }
417 /**
418  * Wrap the lines in the given range. Skip lines that have already
419  * been wrapped.
420  * <p>
421  *
422  * @param startLine first logical line to wrap
423  * @param endLine line after last logical line
424  * @param visualLineIndex visual (wrapped) line index that startLine
425  * corresponds to.
426  * @param width line width to wrap at
427  * @return index of last wrapped line
428  */

429 private int wrapLineRange(int startLine, int endLine, int visualLineIndex, int width) {
430     // if there are no wrapped lines and the width is 0 the widget has
431
// not been made visible/sized yet. don't wrap until the widget size
432
// is known.
433
if (visualLineCount == 0 && width == 0) {
434         return visualLineIndex;
435     }
436
437     for (int i = startLine; i < endLine; i++) {
438         String JavaDoc line = logicalContent.getLine(i);
439         int lineOffset = logicalContent.getOffsetAtLine(i);
440         int lineLength = line.length();
441         if (lineLength == 0) {
442             setVisualLine(visualLineIndex, lineOffset, 0);
443             visualLineIndex++;
444             continue;
445         }
446         TextLayout layout = renderer.getTextLayout(line, lineOffset);
447         layout.setWidth(Math.max(1, width));
448         int[] offsets = layout.getLineOffsets();
449         for (int j = 0; j < offsets.length - 1; j++) {
450             setVisualLine(visualLineIndex++, lineOffset + offsets[j], offsets[j+1] - offsets[j]);
451         }
452         renderer.disposeTextLayout(layout);
453     }
454     return visualLineIndex;
455 }
456 /**
457  * Wrap all logical lines at the current client area width of the
458  * StyledText widget
459  */

460 void wrapLines() {
461     int width = renderer.getClientArea().width - renderer.getLeftMargin() - renderer.getRightMargin();
462     wrapLines(width);
463 }
464 /**
465  * Wrap all logical lines at the given width.
466  * <p>
467  *
468  * @param width width to wrap lines at
469  */

470 void wrapLines(int width) {
471     int lineCount = logicalContent.getLineCount();
472
473     visualLineCount = 0;
474     visualLines = new int[lineCount][2];
475     resetVisualLines(0, visualLines.length);
476     wrapLineRange(0, lineCount, 0, width);
477 }
478 }
479
Popular Tags