001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jface.text;
011:
012: import java.util.ArrayList;
013: import java.util.Iterator;
014: import java.util.NoSuchElementException;
015:
016: import org.eclipse.swt.custom.StyleRange;
017: import org.eclipse.swt.custom.StyledText;
018:
019: import org.eclipse.core.runtime.Assert;
020:
021: /**
022: * Describes the presentation styles for a section of an indexed text such as a
023: * document or string. A text presentation defines a default style for the whole
024: * section and in addition style differences for individual subsections. Text
025: * presentations can be narrowed down to a particular result window. All methods
026: * are result window aware, i.e. ranges outside the result window are always
027: * ignored.
028: * <p>
029: * All iterators provided by a text presentation assume that they enumerate non
030: * overlapping, consecutive ranges inside the default range. Thus, all these
031: * iterators do not include the default range. The default style range must be
032: * explicitly asked for using <code>getDefaultStyleRange</code>.
033: */
034: public class TextPresentation {
035:
036: /**
037: * Applies the given presentation to the given text widget. Helper method.
038: *
039: * @param presentation the style information
040: * @param text the widget to which to apply the style information
041: * @since 2.0
042: */
043: public static void applyTextPresentation(
044: TextPresentation presentation, StyledText text) {
045:
046: StyleRange[] ranges = new StyleRange[presentation
047: .getDenumerableRanges()];
048:
049: int i = 0;
050: Iterator e = presentation.getAllStyleRangeIterator();
051: while (e.hasNext())
052: ranges[i++] = (StyleRange) e.next();
053:
054: text.setStyleRanges(ranges);
055: }
056:
057: /**
058: * Enumerates all the <code>StyleRange</code>s included in the presentation.
059: */
060: class FilterIterator implements Iterator {
061:
062: /** The index of the next style range to be enumerated */
063: protected int fIndex;
064: /** The upper bound of the indices of style ranges to be enumerated */
065: protected int fLength;
066: /** Indicates whether ranges similar to the default range should be enumerated */
067: protected boolean fSkipDefaults;
068: /** The result window */
069: protected IRegion fWindow;
070:
071: /**
072: * <code>skipDefaults</code> tells the enumeration to skip all those style ranges
073: * which define the same style as the presentation's default style range.
074: *
075: * @param skipDefaults <code>false</code> if ranges similar to the default range should be enumerated
076: */
077: protected FilterIterator(boolean skipDefaults) {
078:
079: fSkipDefaults = skipDefaults;
080:
081: fWindow = fResultWindow;
082: fIndex = getFirstIndexInWindow(fWindow);
083: fLength = getFirstIndexAfterWindow(fWindow);
084:
085: if (fSkipDefaults)
086: computeIndex();
087: }
088:
089: /*
090: * @see Iterator#next()
091: */
092: public Object next() {
093: try {
094: StyleRange r = (StyleRange) fRanges.get(fIndex++);
095: return createWindowRelativeRange(fWindow, r);
096: } catch (ArrayIndexOutOfBoundsException x) {
097: throw new NoSuchElementException();
098: } finally {
099: if (fSkipDefaults)
100: computeIndex();
101: }
102: }
103:
104: /*
105: * @see Iterator#hasNext()
106: */
107: public boolean hasNext() {
108: return fIndex < fLength;
109: }
110:
111: /*
112: * @see Iterator#remove()
113: */
114: public void remove() {
115: throw new UnsupportedOperationException();
116: }
117:
118: /**
119: * Returns whether the given object should be skipped.
120: *
121: * @param o the object to be checked
122: * @return <code>true</code> if the object should be skipped by the iterator
123: */
124: protected boolean skip(Object o) {
125: StyleRange r = (StyleRange) o;
126: return r.similarTo(fDefaultRange);
127: }
128:
129: /**
130: * Computes the index of the styled range that is the next to be enumerated.
131: */
132: protected void computeIndex() {
133: while (fIndex < fLength && skip(fRanges.get(fIndex)))
134: ++fIndex;
135: }
136: }
137:
138: /** The style information for the range covered by the whole presentation */
139: private StyleRange fDefaultRange;
140: /** The member ranges of the presentation */
141: private ArrayList fRanges;
142: /** A clipping region against which the presentation can be clipped when asked for results */
143: private IRegion fResultWindow;
144: /**
145: * The optional extent for this presentation.
146: * @since 3.0
147: */
148: private IRegion fExtent;
149:
150: /**
151: * Creates a new empty text presentation.
152: */
153: public TextPresentation() {
154: fRanges = new ArrayList(50);
155: }
156:
157: /**
158: * Creates a new empty text presentation. <code>sizeHint</code> tells the
159: * expected size of this presentation.
160: *
161: * @param sizeHint the expected size of this presentation
162: */
163: public TextPresentation(int sizeHint) {
164: Assert.isTrue(sizeHint > 0);
165: fRanges = new ArrayList(sizeHint);
166: }
167:
168: /**
169: * Creates a new empty text presentation with the given extent.
170: * <code>sizeHint</code> tells the expected size of this presentation.
171: *
172: * @param extent the extent of the created <code>TextPresentation</code>
173: * @param sizeHint the expected size of this presentation
174: * @since 3.0
175: */
176: public TextPresentation(IRegion extent, int sizeHint) {
177: this (sizeHint);
178: Assert.isNotNull(extent);
179: fExtent = extent;
180: }
181:
182: /**
183: * Sets the result window for this presentation. When dealing with
184: * this presentation all ranges which are outside the result window
185: * are ignored. For example, the size of the presentation is 0
186: * when there is no range inside the window even if there are ranges
187: * outside the window. All methods are aware of the result window.
188: *
189: * @param resultWindow the result window
190: */
191: public void setResultWindow(IRegion resultWindow) {
192: fResultWindow = resultWindow;
193: }
194:
195: /**
196: * Set the default style range of this presentation.
197: * The default style range defines the overall area covered
198: * by this presentation and its style information.
199: *
200: * @param range the range describing the default region
201: */
202: public void setDefaultStyleRange(StyleRange range) {
203: fDefaultRange = range;
204: }
205:
206: /**
207: * Returns this presentation's default style range. The returned <code>StyleRange</code>
208: * is relative to the start of the result window.
209: *
210: * @return this presentation's default style range
211: */
212: public StyleRange getDefaultStyleRange() {
213: StyleRange range = createWindowRelativeRange(fResultWindow,
214: fDefaultRange);
215: if (range == null)
216: return null;
217: return (StyleRange) range.clone();
218:
219: }
220:
221: /**
222: * Add the given range to the presentation. The range must be a
223: * subrange of the presentation's default range.
224: *
225: * @param range the range to be added
226: */
227: public void addStyleRange(StyleRange range) {
228: checkConsistency(range);
229: fRanges.add(range);
230: }
231:
232: /**
233: * Replaces the given range in this presentation. The range must be a
234: * subrange of the presentation's default range.
235: *
236: * @param range the range to be added
237: * @since 3.0
238: */
239: public void replaceStyleRange(StyleRange range) {
240: applyStyleRange(range, false);
241: }
242:
243: /**
244: * Merges the given range into this presentation. The range must be a
245: * subrange of the presentation's default range.
246: *
247: * @param range the range to be added
248: * @since 3.0
249: */
250: public void mergeStyleRange(StyleRange range) {
251: applyStyleRange(range, true);
252: }
253:
254: /**
255: * Applies the given range to this presentation. The range must be a
256: * subrange of the presentation's default range.
257: *
258: * @param range the range to be added
259: * @param merge <code>true</code> if the style should be merged instead of replaced
260: * @since 3.0
261: */
262: private void applyStyleRange(StyleRange range, boolean merge) {
263: if (range.length == 0)
264: return;
265:
266: checkConsistency(range);
267:
268: int start = range.start;
269: int length = range.length;
270: int end = start + length;
271:
272: if (fRanges.size() == 0) {
273: StyleRange defaultRange = getDefaultStyleRange();
274: if (defaultRange == null)
275: defaultRange = range;
276:
277: defaultRange.start = start;
278: defaultRange.length = length;
279: applyStyle(range, defaultRange, merge);
280: fRanges.add(defaultRange);
281: } else {
282: IRegion rangeRegion = new Region(start, length);
283: int first = getFirstIndexInWindow(rangeRegion);
284:
285: if (first == fRanges.size()) {
286: StyleRange defaultRange = getDefaultStyleRange();
287: if (defaultRange == null)
288: defaultRange = range;
289: defaultRange.start = start;
290: defaultRange.length = length;
291: applyStyle(range, defaultRange, merge);
292: fRanges.add(defaultRange);
293: return;
294: }
295:
296: int last = getFirstIndexAfterWindow(rangeRegion);
297: for (int i = first; i < last && length > 0; i++) {
298:
299: StyleRange current = (StyleRange) fRanges.get(i);
300: int currentStart = current.start;
301: int currentEnd = currentStart + current.length;
302:
303: if (end <= currentStart) {
304: fRanges.add(i, range);
305: return;
306: }
307:
308: if (start >= currentEnd)
309: continue;
310:
311: StyleRange currentCopy = null;
312: if (end < currentEnd)
313: currentCopy = (StyleRange) current.clone();
314:
315: if (start < currentStart) {
316: // Apply background to new default range and add it
317: StyleRange defaultRange = getDefaultStyleRange();
318: if (defaultRange == null)
319: defaultRange = new StyleRange();
320:
321: defaultRange.start = start;
322: defaultRange.length = currentStart - start;
323: applyStyle(range, defaultRange, merge);
324: fRanges.add(i, defaultRange);
325: i++;
326: last++;
327:
328: // Apply background to first part of current range
329: current.length = Math.min(end, currentEnd)
330: - currentStart;
331: applyStyle(range, current, merge);
332: }
333:
334: if (start >= currentStart) {
335: // Shorten the current range
336: current.length = start - currentStart;
337:
338: // Apply the background to the rest of the current range and add it
339: if (current.length > 0) {
340: current = (StyleRange) current.clone();
341: i++;
342: last++;
343: fRanges.add(i, current);
344: }
345: applyStyle(range, current, merge);
346: current.start = start;
347: current.length = Math.min(end, currentEnd) - start;
348: }
349:
350: if (end < currentEnd) {
351: // Add rest of current range
352: currentCopy.start = end;
353: currentCopy.length = currentEnd - end;
354: i++;
355: last++;
356: fRanges.add(i, currentCopy);
357: }
358:
359: // Update range
360: range.start = currentEnd;
361: range.length = Math.max(end - currentEnd, 0);
362: start = range.start;
363: length = range.length;
364: }
365: if (length > 0) {
366: // Apply background to new default range and add it
367: StyleRange defaultRange = getDefaultStyleRange();
368: if (defaultRange == null)
369: defaultRange = range;
370: defaultRange.start = start;
371: defaultRange.length = end - start;
372: defaultRange.background = range.background;
373: fRanges.add(last, defaultRange);
374: }
375: }
376: }
377:
378: /**
379: * Replaces the given ranges in this presentation. Each range must be a
380: * subrange of the presentation's default range. The ranges must be ordered
381: * by increasing offset and must not overlap (but may be adjacent).
382: *
383: * @param ranges the ranges to be added
384: * @since 3.0
385: */
386: public void replaceStyleRanges(StyleRange[] ranges) {
387: applyStyleRanges(ranges, false);
388: }
389:
390: /**
391: * Merges the given ranges into this presentation. Each range must be a
392: * subrange of the presentation's default range. The ranges must be ordered
393: * by increasing offset and must not overlap (but may be adjacent).
394: *
395: * @param ranges the ranges to be added
396: * @since 3.0
397: */
398: public void mergeStyleRanges(StyleRange[] ranges) {
399: applyStyleRanges(ranges, true);
400: }
401:
402: /**
403: * Applies the given ranges to this presentation. Each range must be a
404: * subrange of the presentation's default range. The ranges must be ordered
405: * by increasing offset and must not overlap (but may be adjacent).
406: *
407: * @param ranges the ranges to be added
408: * @param merge <code>true</code> if the style should be merged instead of replaced
409: * @since 3.0
410: */
411: private void applyStyleRanges(StyleRange[] ranges, boolean merge) {
412: int j = 0;
413: ArrayList oldRanges = fRanges;
414: ArrayList newRanges = new ArrayList(2 * ranges.length
415: + oldRanges.size());
416: for (int i = 0, n = ranges.length; i < n; i++) {
417: StyleRange range = ranges[i];
418: fRanges = oldRanges; // for getFirstIndexAfterWindow(...)
419: for (int m = getFirstIndexAfterWindow(new Region(
420: range.start, range.length)); j < m; j++)
421: newRanges.add(oldRanges.get(j));
422: fRanges = newRanges; // for mergeStyleRange(...)
423: applyStyleRange(range, merge);
424: }
425: for (int m = oldRanges.size(); j < m; j++)
426: newRanges.add(oldRanges.get(j));
427: fRanges = newRanges;
428: }
429:
430: /**
431: * Applies the template's style to the target.
432: *
433: * @param template the style range to be used as template
434: * @param target the style range to which to apply the template
435: * @param merge <code>true</code> if the style should be merged instead of replaced
436: * @since 3.0
437: */
438: private void applyStyle(StyleRange template, StyleRange target,
439: boolean merge) {
440: if (merge) {
441: if (template.font != null)
442: target.font = template.font;
443: if (template.metrics != null)
444: target.metrics = template.metrics;
445: if (template.foreground != null)
446: target.foreground = template.foreground;
447: if (template.background != null)
448: target.background = template.background;
449: target.fontStyle |= template.fontStyle;
450: target.strikeout = template.strikeout || target.strikeout;
451: target.underline = template.underline || target.underline;
452: } else {
453: target.foreground = template.foreground;
454: target.background = template.background;
455: target.fontStyle = template.fontStyle;
456: target.strikeout = template.strikeout;
457: target.underline = template.underline;
458: target.font = template.font;
459: target.metrics = template.metrics;
460: }
461: }
462:
463: /**
464: * Checks whether the given range is a subrange of the presentation's
465: * default style range.
466: *
467: * @param range the range to be checked
468: * @exception IllegalArgumentException if range is not a subrange of the presentation's default range
469: */
470: private void checkConsistency(StyleRange range) {
471:
472: if (range == null)
473: throw new IllegalArgumentException();
474:
475: if (fDefaultRange != null) {
476:
477: if (range.start < fDefaultRange.start)
478: range.start = fDefaultRange.start;
479:
480: int defaultEnd = fDefaultRange.start + fDefaultRange.length;
481: int end = range.start + range.length;
482: if (end > defaultEnd)
483: range.length -= (end - defaultEnd);
484: }
485: }
486:
487: /**
488: * Returns the index of the first range which overlaps with the
489: * specified window.
490: *
491: * @param window the window to be used for searching
492: * @return the index of the first range overlapping with the window
493: */
494: private int getFirstIndexInWindow(IRegion window) {
495: if (window != null) {
496: int start = window.getOffset();
497: int i = -1, j = fRanges.size();
498: while (j - i > 1) {
499: int k = (i + j) >> 1;
500: StyleRange r = (StyleRange) fRanges.get(k);
501: if (r.start + r.length > start)
502: j = k;
503: else
504: i = k;
505: }
506: return j;
507: }
508: return 0;
509: }
510:
511: /**
512: * Returns the index of the first range which comes after the specified window and does
513: * not overlap with this window.
514: *
515: * @param window the window to be used for searching
516: * @return the index of the first range behind the window and not overlapping with the window
517: */
518: private int getFirstIndexAfterWindow(IRegion window) {
519: if (window != null) {
520: int end = window.getOffset() + window.getLength();
521: int i = -1, j = fRanges.size();
522: while (j - i > 1) {
523: int k = (i + j) >> 1;
524: StyleRange r = (StyleRange) fRanges.get(k);
525: if (r.start < end)
526: i = k;
527: else
528: j = k;
529: }
530: return j;
531: }
532: return fRanges.size();
533: }
534:
535: /**
536: * Returns a style range which is relative to the specified window and
537: * appropriately clipped if necessary. The original style range is not
538: * modified.
539: *
540: * @param window the reference window
541: * @param range the absolute range
542: * @return the window relative range based on the absolute range
543: */
544: private StyleRange createWindowRelativeRange(IRegion window,
545: StyleRange range) {
546: if (window == null || range == null)
547: return range;
548:
549: int start = range.start - window.getOffset();
550: if (start < 0)
551: start = 0;
552:
553: int rangeEnd = range.start + range.length;
554: int windowEnd = window.getOffset() + window.getLength();
555: int end = (rangeEnd > windowEnd ? windowEnd : rangeEnd);
556: end -= window.getOffset();
557:
558: StyleRange newRange = (StyleRange) range.clone();
559: newRange.start = start;
560: newRange.length = end - start;
561: return newRange;
562: }
563:
564: /**
565: * Returns the region which is relative to the specified window and
566: * appropriately clipped if necessary.
567: *
568: * @param coverage the absolute coverage
569: * @return the window relative region based on the absolute coverage
570: * @since 3.0
571: */
572: private IRegion createWindowRelativeRegion(IRegion coverage) {
573: if (fResultWindow == null || coverage == null)
574: return coverage;
575:
576: int start = coverage.getOffset() - fResultWindow.getOffset();
577: if (start < 0)
578: start = 0;
579:
580: int rangeEnd = coverage.getOffset() + coverage.getLength();
581: int windowEnd = fResultWindow.getOffset()
582: + fResultWindow.getLength();
583: int end = (rangeEnd > windowEnd ? windowEnd : rangeEnd);
584: end -= fResultWindow.getOffset();
585:
586: return new Region(start, end - start);
587: }
588:
589: /**
590: * Returns an iterator which enumerates all style ranged which define a style
591: * different from the presentation's default style range. The default style range
592: * is not enumerated.
593: *
594: * @return a style range iterator
595: */
596: public Iterator getNonDefaultStyleRangeIterator() {
597: return new FilterIterator(fDefaultRange != null);
598: }
599:
600: /**
601: * Returns an iterator which enumerates all style ranges of this presentation
602: * except the default style range. The returned <code>StyleRange</code>s
603: * are relative to the start of the presentation's result window.
604: *
605: * @return a style range iterator
606: */
607: public Iterator getAllStyleRangeIterator() {
608: return new FilterIterator(false);
609: }
610:
611: /**
612: * Returns whether this collection contains any style range including
613: * the default style range.
614: *
615: * @return <code>true</code> if there is no style range in this presentation
616: */
617: public boolean isEmpty() {
618: return (fDefaultRange == null && getDenumerableRanges() == 0);
619: }
620:
621: /**
622: * Returns the number of style ranges in the presentation not counting the default
623: * style range.
624: *
625: * @return the number of style ranges in the presentation excluding the default style range
626: */
627: public int getDenumerableRanges() {
628: int size = getFirstIndexAfterWindow(fResultWindow)
629: - getFirstIndexInWindow(fResultWindow);
630: return (size < 0 ? 0 : size);
631: }
632:
633: /**
634: * Returns the style range with the smallest offset ignoring the default style range or null
635: * if the presentation is empty.
636: *
637: * @return the style range with the smallest offset different from the default style range
638: */
639: public StyleRange getFirstStyleRange() {
640: try {
641:
642: StyleRange range = (StyleRange) fRanges
643: .get(getFirstIndexInWindow(fResultWindow));
644: return createWindowRelativeRange(fResultWindow, range);
645:
646: } catch (NoSuchElementException x) {
647: } catch (IndexOutOfBoundsException x) {
648: }
649:
650: return null;
651: }
652:
653: /**
654: * Returns the style range with the highest offset ignoring the default style range.
655: *
656: * @return the style range with the highest offset different from the default style range
657: */
658: public StyleRange getLastStyleRange() {
659: try {
660:
661: StyleRange range = (StyleRange) fRanges
662: .get(getFirstIndexAfterWindow(fResultWindow) - 1);
663: return createWindowRelativeRange(fResultWindow, range);
664:
665: } catch (NoSuchElementException x) {
666: return null;
667: } catch (IndexOutOfBoundsException x) {
668: return null;
669: }
670: }
671:
672: /**
673: * Returns the coverage of this presentation as clipped by the presentation's
674: * result window.
675: *
676: * @return the coverage of this presentation
677: */
678: public IRegion getCoverage() {
679:
680: if (fDefaultRange != null) {
681: StyleRange range = getDefaultStyleRange();
682: return new Region(range.start, range.length);
683: }
684:
685: StyleRange first = getFirstStyleRange();
686: StyleRange last = getLastStyleRange();
687:
688: if (first == null || last == null)
689: return null;
690:
691: return new Region(first.start, last.start - first.start
692: + last.length);
693: }
694:
695: /**
696: * Returns the extent of this presentation clipped by the
697: * presentation's result window.
698: *
699: * @return the clipped extent
700: * @since 3.0
701: */
702: public IRegion getExtent() {
703: if (fExtent != null)
704: return createWindowRelativeRegion(fExtent);
705: return getCoverage();
706: }
707:
708: /**
709: * Clears this presentation by resetting all applied changes.
710: * @since 2.0
711: */
712: public void clear() {
713: fDefaultRange = null;
714: fResultWindow = null;
715: fRanges.clear();
716: }
717:
718: }
|