KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*******************************************************************************
2  * Copyright (c) 2000, 2005 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 import org.eclipse.swt.graphics.*;
14 import org.eclipse.swt.internal.Compatibility;
15 import java.util.Vector JavaDoc;
16
17 class DefaultLineStyler implements LineStyleListener, LineBackgroundListener {
18     StyledTextContent content;
19     StyleRange styles[] = new StyleRange[0];
20     int styleCount = 0; // the number of styles
21
int lineExpandExp = 1; // the expansion exponent, used to increase the lines array exponentially
22
int lineCount = 0;
23     Color lineBackgrounds[];
24     
25 /**
26  * Creates a new default line styler.
27  * <p>
28  *
29  * @param content the text to which the styles apply
30  */

31 public DefaultLineStyler(StyledTextContent content) {
32     this.content = content;
33     lineCount = content.getLineCount();
34     lineBackgrounds = new Color[lineCount];
35 }
36 /**
37  * Inserts a style at the given location.
38  * <p>
39  *
40  * @param style the new style
41  * @param index the index at which to insert the style (the new style
42  * will reside at this index)
43  *
44  */

45 void insertStyle(StyleRange style, int index) {
46     insertStyles(new StyleRange[] {style}, index);
47 }
48 /**
49  * Insert the styles at the given location.
50  * <p>
51  *
52  * @param insertStyles the new styles
53  * @param index the index at which to insert the styles (the first new style
54  * will reside at this index)
55  *
56  */

57 void insertStyles(StyleRange[] insertStyles, int index) {
58     int size = styles.length;
59     int insertCount = insertStyles.length;
60     int spaceNeeded = styleCount + insertCount - size;
61     if (spaceNeeded > 0) {
62         StyleRange[] newStyles = new StyleRange[size+spaceNeeded];
63         System.arraycopy(styles, 0, newStyles, 0, size);
64         styles = newStyles;
65     }
66     // shift the styles down to make room for the new styles
67
System.arraycopy(styles, index, styles, index + insertCount, styleCount - index);
68     // add the new styles
69
System.arraycopy(insertStyles, 0, styles, index, insertCount);
70     styleCount = styleCount + insertCount;
71 }
72 /**
73  * Inserts a style, merging it with adjacent styles if possible.
74  * <p>
75  *
76  * @param style the new style
77  * @param index the index at which to insert the style (the new style
78  * will reside at this index)
79  * @return true if the style was inserted, false if the style was merged with an adjacent
80  * style
81  */

82 boolean insertMergeStyle(StyleRange style, int index) {
83     if (mergeStyleBefore(style, index)) return false;
84     if (mergeStyleAfter(style, index)) return false;
85     insertStyle(style, index);
86     return true;
87 }
88 /**
89  * Merges the style with the style before it if possible.
90  * <p>
91  *
92  * @param style the new style
93  * @param index the index at which to attempt the merge.
94  * @return true if the style was merged, false otherwise
95  */

96 boolean mergeStyleBefore(StyleRange style, int index) {
97     // see if the style is similar to the style before it and merge the
98
// styles if possible
99
if (index > 0) {
100         StyleRange previous = styles[index-1];
101         if (style.similarTo(previous)) {
102             // the start of style needs to be in the range of the previous style
103
// and the end of style needs to be < the start of the next style
104
int previousEnd = previous.start + previous.length;
105             if ((style.start <= previousEnd) && (style.start >= previous.start)) {
106                 int styleEnd = style.start + style.length;
107                 if ((index == styleCount) || (styleEnd <= styles[index].start)) {
108                     previous.length = style.start + style.length - previous.start;
109                     return true;
110                 }
111             }
112         }
113     }
114     return false;
115 }
116 /**
117  * Merges the style with the style after it if possible.
118  * <p>
119  *
120  * @param style the new style
121  * @param index the index at which to attempt the merge.
122  * @return true if the style was merged, false otherwise
123  */

124 boolean mergeStyleAfter(StyleRange style, int index) {
125     // see if the style is similar to the style that will be after it and
126
// merge the styles if possible
127
if (index < styleCount) {
128         StyleRange next = styles[index];
129         if (style.similarTo(next)) {
130             // the end of style needs to be in the range of the next style and
131
// the start of style needs to be > the end of the previous style
132
int styleEnd = style.start + style.length;
133             int nextEnd = next.start + next.length;
134             if ((styleEnd <= nextEnd) && (styleEnd >= next.start)) {
135                 if ((index == 0) || (style.start >= styles[index-1].start + styles[index-1].length)) {
136                     next.length = next.start + next.length - style.start;
137                     next.start = style.start;
138                     return true;
139                 }
140             }
141         }
142     }
143     return false;
144 }
145 /**
146  * Removes style information that is defined for the range of text in <code>clearStyle</code>.
147  * <p>
148  *
149  * @param clearStyle the style information to use for clearing
150  */

151 void clearStyle(StyleRange clearStyle) {
152     Point pt = getOverlappingStyles(clearStyle.start, clearStyle.length);
153     int clearStyleEnd = clearStyle.start + clearStyle.length - 1;
154     
155     // no overlapped styles exist
156
if ((pt == null) || (pt.y == 0)) return;
157
158     // the newStyle overlaps one or more of the existing styles
159
// pt.x is the index of the first overlapped style, pt.y is the number of overlapped
160
// styles
161
int count = 0;
162     int deleteStyle = -1;
163     int deleteCount = 0;
164     for (int i=pt.x; count<pt.y; i++) {
165         StyleRange overlap = styles[i];
166         int overlapEnd = overlap.start + overlap.length - 1;
167         if (overlap.start < clearStyle.start) {
168             if (overlapEnd <= clearStyleEnd) {
169                 // the end of overlap needs to be cleared
170
overlap.length=clearStyle.start - overlap.start;
171             } else {
172                 // middle of overlap needs to be cleared, this will
173
// cause overlap to be broken into two
174
StyleRange endStyle = (StyleRange)overlap.clone();
175                 endStyle.start = clearStyleEnd + 1;
176                 endStyle.length = overlapEnd - clearStyleEnd;
177                 overlap.length = clearStyle.start - overlap.start;
178                 insertStyle(endStyle, i+1);
179                 break;
180             }
181         } else {
182             if (overlapEnd <= clearStyleEnd) {
183                 // entire overlap needs to be cleared
184
if (deleteStyle == -1) {
185                     deleteStyle = i;
186                 }
187                 deleteCount++;
188             } else {
189                 // beginning of overlap needs to be cleared
190
overlap.start=clearStyleEnd + 1;
191                 overlap.length=overlapEnd - overlap.start + 1;
192                 break;
193             }
194         }
195         count++;
196     }
197     deleteStyles(deleteStyle, deleteCount);
198 }
199 /**
200  * Increases the <code>linebackgrounds</code> array to accomodate new line background
201  * information.
202  * <p>
203  *
204  * @param numLines the number to increase the array by
205  */

206 void expandLinesBy(int numLines) {
207     int size = lineBackgrounds.length;
208     if (size - lineCount >= numLines) {
209         return;
210     }
211     Color[] newLines = new Color[size+Math.max(Compatibility.pow2(lineExpandExp), numLines)];
212     System.arraycopy(lineBackgrounds, 0, newLines, 0, size);
213     lineBackgrounds = newLines;
214     lineExpandExp++;
215 }
216 /**
217  * Deletes the style at <code>index</code>.
218  * <p>
219  *
220  * @param index the index of the style to be deleted
221  */

222 void deleteStyle(int index) {
223     deleteStyles(index, 1);
224 }
225 /**
226  * Delete count styles starting at <code>index</code>.
227  * <p>
228  *
229  * @param index the index of the style to be deleted
230  * @param count the number of styles to be deleted
231  */

232 void deleteStyles(int index, int count) {
233     if ((count == 0) || (index < 0)) return;
234     // shift the styles up
235
System.arraycopy(styles, index + count, styles, index, styleCount - (index + count));
236     for (int i=0; i<count; i++) {
237         styles[styleCount - i - 1] = null;
238     }
239     styleCount = styleCount - count;
240 }
241 /**
242  * Returns the styles that are defined.
243  * <p>
244  *
245  * @return the copied array of styles
246  */

247 StyleRange [] getStyleRanges() {
248     StyleRange[] newStyles = new StyleRange[styleCount];
249     System.arraycopy(styles, 0, newStyles, 0, styleCount);
250     return newStyles;
251 }
252 /**
253  * Handles the get line background color callback.
254  * <p>
255  *
256  * @param event the lineOffset line number (input), lineText line text (input),
257  * and background line background color (output)
258  */

259 public void lineGetBackground(LineBackgroundEvent event) {
260     int lineIndex = content.getLineAtOffset(event.lineOffset);
261     event.lineBackground = lineBackgrounds[lineIndex];
262 }
263 /**
264  * Handles the get line style information callback.
265  * <p>
266  *
267  * @param event the lineOffset line number (input), lineText line text (input),
268  * and styles array of StyleRanges, need to be in order (output)
269  */

270 public void lineGetStyle(LineStyleEvent event) {
271     int lineStart = event.lineOffset;
272     int lineEnd = lineStart + event.lineText.length();
273
274     int high = searchForStyle(lineStart, lineEnd);
275     StyleRange style = null;
276     Vector JavaDoc lineStyles = new Vector JavaDoc();
277
278     // index will represent a style that
279
// -- starts after the line (end processing)
280
// -- ends before the line (continue processing)
281
// -- starts before the line, ends in the line (add range)
282
// -- starts in the line, ends in the line (add range)
283
// -- starts in the line, ends after the line (add range)
284
// -- starts before the line, ends after the line (add range)
285
for (int index = high; index < styleCount; index++) {
286         style = styles[index];
287         if (style.start > lineEnd)
288             // style starts after the line, end looping
289
break;
290         int styleEnd = style.start + style.length - 1;
291         if (styleEnd >= lineStart) lineStyles.addElement(style);
292     }
293     event.styles = new StyleRange[lineStyles.size()];
294     lineStyles.copyInto(event.styles);
295 }
296 /**
297  * Searches for the first style in the <code>start</code> - <code>end</code> range.
298  * <p>
299  *
300  * @return the index of the first style that overlaps the input range
301  */

302 int searchForStyle(int start, int end) {
303     int high = styleCount;
304     int low = -1;
305     int index = high;
306     // find the index of the first style for the given range, use a binary search
307
while (high - low > 1) {
308         index = (high + low) / 2;
309         StyleRange style = styles[index];
310         int styleEnd = style.start + style.length - 1;
311         if (start <= style.start || end <= styleEnd || (start > style.start && styleEnd >= start && styleEnd < end)) {
312             high = index;
313         }
314         else {
315             low = index;
316         }
317     }
318     return high;
319 }
320 /**
321  * Updates the line background colors to reflect a new color. Called by StyledText.
322  * <p>
323  *
324  * @param startLine index of the first line to color
325  * @param count number of lines to color starting at startLine
326  * @param background the background color for the lines
327  */

328 void setLineBackground(int startLine, int count, Color background) {
329     for (int i=startLine; i<startLine + count; i++) {
330         lineBackgrounds[i]=background;
331     }
332 }
333 /**
334  * Update the styles to reflect the new style. <code>newStyle</code> will
335  * replace any old style for the range. When this method is called, the
336  * DefaultLineStyler may merge the new style with an existing style (if possible).
337  * Called by StyledText when a style is added. Called by StyledText.
338  * <p>
339  *
340  * @param newStyle the new style information.
341  */

342 void setStyleRange(StyleRange newStyle) {
343     if (newStyle == null) {
344         styles = new StyleRange[0];
345         styleCount = 0;
346         return;
347     }
348     if (newStyle.length ==0) return;
349     if (newStyle.isUnstyled()) {
350         clearStyle(newStyle);
351         return;
352     }
353     
354     Point pt = getOverlappingStyles(newStyle.start, newStyle.length);
355     int newStyleEnd = newStyle.start + newStyle.length - 1;
356     
357     // no styles exist
358
if (pt == null) {
359         insertStyle(newStyle, 0);
360         return;
361     }
362     
363     // newStyle does not overlap any other styles
364
if (pt.y == 0) {
365         insertMergeStyle(newStyle, pt.x);
366         return;
367     }
368
369     // the newStyle overlaps one or more of the existing styles
370
boolean added = false; // indicates whether or not the new style has been added
371
int count = 0;
372     // pt.x is the index of the first overlapped style, pt.y is the number of overlapped
373
// styles
374
for (int i=pt.x; count<pt.y; i++) {
375         StyleRange overlap = styles[i];
376         int overlapEnd = overlap.start + overlap.length - 1;
377         if (overlap.start < newStyle.start) {
378             if (overlapEnd <= newStyleEnd) {
379                 // the end of overlap needs to be replaced by newStyle
380
if (newStyle.similarTo(overlap)) {
381                     // update overlap to accomodate the new style
382
overlap.length = newStyle.start + newStyle.length - overlap.start;
383                 } else {
384                     overlap.length=newStyle.start - overlap.start;
385                     // see if newStyle can be merged with the style after overlap, if so,
386
// processing is done
387
if (mergeStyleAfter(newStyle, i+1)) break;
388                     // otherwise, insert the newStyle, newStyle may still overlap other
389
// styles after it so continue processing
390
insertStyle(newStyle, i+1);
391                     i++;
392                 }
393                 added = true;
394             } else {
395                 // middle of overlap needs to be replaced by newStyle, this will
396
// cause overlap to be broken into two
397
if (newStyle.similarTo(overlap)) break;
398                 StyleRange endStyle = (StyleRange)overlap.clone();
399                 endStyle.start = newStyleEnd + 1;
400                 endStyle.length = overlapEnd - newStyleEnd;
401                 overlap.length = newStyle.start - overlap.start;
402                 insertStyle(newStyle, i+1);
403                 i++;
404                 insertStyle(endStyle, i+1);
405                 // when newStyle overlaps the middle of a style, this implies that
406
// processing is done (no more overlapped styles)
407
break;
408             }
409         } else {
410             if (overlapEnd <= newStyleEnd) {
411                 // overlap will be replaced by the newStyle, make sure newStyle
412
// hasn't already been added, if it has just delete overlap
413
if (!added) {
414                     styles[i] = newStyle;
415                     added = true;
416                 } else {
417                     deleteStyle(i);
418                     i--;
419                 }
420             } else {
421                 // beginning of overlap needs to be replaced by newStyle
422
overlap.start=newStyleEnd + 1;
423                 overlap.length=overlapEnd - overlap.start + 1;
424                 if (!added) {
425                     insertMergeStyle(newStyle, i);
426                 }
427                 // when newStyle overlaps only the beginning of a style, this implies
428
// that processing is done (no more overlapped styles)
429
break;
430             }
431         }
432     count++;
433     }
434 }
435 /**
436  * Replace the styles for the given range.
437  *
438  * @param start the initial style range to replace
439  * @param length the number of ranges to replace
440  * @param ranges the new styles, must be in order and non-overlapping
441  */

442 void replaceStyleRanges(int start, int length, StyleRange[] ranges) {
443     clearStyle(new StyleRange(start, length, null, null));
444     // find insert point
445
int high = styleCount;
446     int low = -1;
447     int index = high;
448     while (high - low > 1) {
449         index = (high + low) / 2;
450         StyleRange style = styles[index];
451         if (start <= style.start) {
452             high = index;
453         }
454         else {
455             low = index;
456         }
457     }
458     insertStyles(ranges, high);
459 }
460 /**
461  * Sets the array of styles and discards old styles. Called by StyledText.
462  * <p>
463  *
464  * @param styles the new styles, must be in order and non-overlapping
465  */

466 void setStyleRanges(StyleRange[] styles) {
467     this.styles = new StyleRange[styles.length];
468     System.arraycopy(styles, 0, this.styles, 0, styles.length);
469     styleCount = styles.length;
470 }
471 /**
472  * Updates the style ranges and line backgrounds to reflect a pending text
473  * change.
474  * Called by StyledText when a TextChangingEvent is received.
475  * <p>
476  *
477  * @param event the event with the text change information
478  */

479 public void textChanging(TextChangingEvent event) {
480     int startLine = content.getLineAtOffset(event.start);
481     int startLineOffset = content.getOffsetAtLine(startLine);
482     
483     textChanging(event.start, -event.replaceCharCount);
484     textChanging(event.start, event.newCharCount);
485     
486     if (event.replaceCharCount == content.getCharCount()) {
487         // all text is going to be replaced, clear line backgrounds
488
linesChanging(0, -lineCount);
489         linesChanging(0, content.getLineCount() - event.replaceLineCount + event.newLineCount);
490         return;
491     }
492
493     if (event.start != startLineOffset) {
494         startLine = startLine + 1;
495     }
496             
497     linesChanging(startLine, -event.replaceLineCount);
498     linesChanging(startLine, event.newLineCount);
499 }
500 /*
501  * Updates the line backgrounds to reflect a pending text change.
502  * <p>
503  *
504  * @param start the starting line of the change that is about to take place
505  * @param delta the number of lines in the change, > 0 indicates lines inserted,
506  * < 0 indicates lines deleted
507  */

508 void linesChanging(int start, int delta) {
509     if (delta == 0) return;
510     boolean inserting = delta > 0;
511     if (inserting) {
512         // shift the lines down to make room for new lines
513
expandLinesBy(delta);
514         for (int i = lineCount-1; i >= start; i--) {
515             lineBackgrounds[i + delta]=lineBackgrounds[i];
516         }
517         for (int i=start; i<start + delta; i++) {
518             lineBackgrounds[i]=null;
519         }
520     } else {
521         // shift up the lines
522
for (int i = start - delta; i < lineCount; i++) {
523             lineBackgrounds[i+delta]=lineBackgrounds[i];
524         }
525     }
526     lineCount += delta;
527 }
528 /*
529  * Updates the style ranges to reflect a text change.
530  * <p>
531  *
532  * @param start the starting offset of the change that is about to
533  * take place
534  * @param delta the length of the change, > 0 indicates text inserted,
535  * < 0 indicates text deleted
536  */

537 void textChanging(int start, int delta) {
538     if (delta == 0) return;
539     StyleRange style;
540     // find the index of the first style for the given offset, use a binary search
541
// to find the index
542
int end;
543     int deleteStart = -1;
544     int deleteCount = 0;
545     boolean inserting = delta > 0;
546     if (inserting) {
547         end = (start + delta) - 1;
548     } else {
549         end = (start - delta) - 1;
550     }
551     int high = searchForStyle(start, end);
552     int index;
553     // update the styles that are in the affected range
554
for (index = high; index < styleCount; index++) {
555         style = styles[index];
556         if (inserting) {
557             if (style.start >= start) break;
558             // in the insert case only one style range will be directly affected,
559
// it will need to be split into two and then the newStyle inserted
560
StyleRange beforeStyle = (StyleRange)style.clone();
561             beforeStyle.length = start - style.start;
562             style.start = start;
563             style.length = style.length - beforeStyle.length;
564             if (beforeStyle.length != 0) insertStyle(beforeStyle, index);
565             index++;
566             break;
567         } else {
568             int styleEnd = style.start + style.length - 1;
569             if (style.start > end) break;
570             // in the delete case, any style that overlaps the change range will be
571
// affected
572
if (style.start < start) {
573                 if (styleEnd <= end) {
574                     // style starts before change range, ends in change range
575
style.length = start - style.start;
576                 } else {
577                     // style starts before change range, ends after change range
578
style.length = style.length + delta;
579                     index++;
580                     break;
581                 }
582             } else {
583                 if (styleEnd <= end) {
584                     // style starts in change range, ends in change range
585
if (deleteStart == -1) {
586                         deleteStart = index;
587                     }
588                     deleteCount++;
589                 } else {
590                     // style starts in change range, ends after change range
591
style.start = start;
592                     style.length = styleEnd - end;
593                     index++;
594                     break;
595                 }
596             }
597         }
598     }
599     deleteStyles(deleteStart, deleteCount);
600     // change the offsets of the styles after the affected styles
601
for (int i = index - deleteCount; i < styleCount; i++) {
602         style = styles[i];
603         style.start = style.start + delta;
604     }
605 }
606 /**
607  * Returns the indexes of the styles that overlap the given range. Styles that partially
608  * or fully overlap the range will be returned.
609  * <p>
610  *
611  * @return Point where x is the index of the starting overlap style, y is the number of
612  * styles that overlap the range
613  */

614 Point getOverlappingStyles(int start, int length) {
615     StyleRange style;
616     if (styleCount == 0) return null;
617     // find the index of the first style for the given offset, use a binary search
618
// to find the index
619
int end = start + length - 1;
620     int high = searchForStyle(start, end);
621     int count = 0;
622     for (int index = high; index < styleCount; index++) {
623         style = styles[index];
624         int styleEnd = style.start + style.length - 1;
625         if (style.start > end) break;
626         if (styleEnd >= start) count++;
627     }
628     return new Point(high, count);
629 }
630 /**
631  * Returns the background color of a line. Called by StyledText. It is safe to return
632  * the existing Color object since the colors are set and managed by the client.
633  * <p>
634  *
635  * @param index the line index
636  * @return the background color of the line at the given index
637  */

638 Color getLineBackground(int index) {
639     return lineBackgrounds[index];
640 }
641 /**
642  * Returns the style for the character at <code>offset</code>. Called by StyledText.
643  * Returns a new style. Does not return the existing style.
644  * <p>
645  *
646  * @param offset the character position in the text
647  * @return a cloned StyleRange with start == offset and length == 1 if a style is
648  * specified or null if no style is specified
649  */

650 StyleRange getStyleRangeAtOffset(int offset) {
651     if (styleCount == 0) return null;
652     Point pt = getOverlappingStyles(offset, 1);
653     if (pt == null || pt.y == 0) return null;
654     StyleRange newStyle = (StyleRange)styles[pt.x].clone();
655     newStyle.start = offset;
656     newStyle.length = 1;
657     return newStyle;
658 }
659 /**
660  * Returns the styles for the given range. Returns the existing styles,
661  * so be careful not to modify the return value. Styles are not cloned
662  * in order to make this method as efficient as possible.
663  * <p>
664  *
665  * @param offset the start position of the text range
666  * @param length the length of the text range
667  * @return a StyleRange array or null if no styles are specified for the text
668  * range
669  */

670 StyleRange[] getStyleRangesFor(int offset, int length) {
671     if (styleCount == 0) return null;
672     Point pt = getOverlappingStyles(offset, length);
673     if (pt == null || pt.y == 0) return null;
674     StyleRange[] ranges = new StyleRange[pt.y];
675     for (int i=0; i<pt.y; i++) {
676         StyleRange newStyle = styles[pt.x + i];
677         ranges[i]=newStyle;
678     }
679     return ranges;
680 }
681 void release () {
682     styles = null;
683 }
684 }
685
Popular Tags