root/dwt/custom/DefaultContent.d

Revision 274:62a03a4c21c8, 29.9 kB (checked in by Frank Benoit <benoit@tionex.de>, 5 months ago)

sync StyledText? with dwt-linux

Line 
1 /*******************************************************************************
2  * Copyright (c) 2000, 2008 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  * Port to the D programming language:
11  *     Frank Benoit <benoit@tionex.de>
12  *******************************************************************************/
13 module dwt.custom.DefaultContent;
14
15 import dwt.DWT;
16 import dwt.DWTException;
17 import dwt.internal.Compatibility;
18 import dwt.widgets.TypedListener;
19 import dwt.custom.StyledTextContent;
20 import dwt.custom.TextChangeListener;
21 import dwt.custom.StyledTextEvent;
22 import dwt.custom.StyledTextListener;
23 import dwt.custom.StyledText;
24 import dwt.dwthelper.utils;
25
26 static import tango.io.model.IFile;
27 static import tango.text.Text;
28
29 alias tango.text.Text.Text!(char) StringBuffer;
30
31 class DefaultContent : StyledTextContent {
32     private final static String LineDelimiter = tango.io.model.IFile.FileConst.NewlineString;
33
34     StyledTextListener[] textListeners; // stores text listeners for event sending
35     char[] textStore; // stores the actual text
36     int gapStart = -1;  // the character position start of the gap
37     int gapEnd = -1;    // the character position after the end of the gap
38     int gapLine = -1;   // the line on which the gap exists, the gap will always be associated with one line
39     int highWatermark = 300;
40     int lowWatermark = 50;
41
42     int[][] lines; // array of character positions and lengths representing the lines of text
43     int lineCount_ = 0;  // the number of lines of text
44     int expandExp = 1;  // the expansion exponent, used to increase the lines array exponentially
45     int replaceExpandExp = 1;   // the expansion exponent, used to increase the lines array exponentially
46
47 /**
48  * Creates a new DefaultContent and initializes it.  A <code>StyledTextContent</> will always have
49  * at least one empty line.
50  */
51 this() {
52     lines = new int[][]( 50, 2 );
53     setText("");
54 }
55 /**
56  * Adds a line to the end of the line indexes array.  Increases the size of the array if necessary.
57  * <code>lineCount</code> is updated to reflect the new entry.
58  * <p>
59  *
60  * @param start the start of the line
61  * @param length the length of the line
62  */
63 void addLineIndex(int start, int length) {
64     int size = lines.length;
65     if (lineCount_ is size) {
66         // expand the lines by powers of 2
67         int[][] newLines = new int[][]( size+Compatibility.pow2(expandExp), 2 );
68         System.arraycopy(lines, 0, newLines, 0, size);
69         lines = newLines;
70         expandExp++;
71     }
72     int[] range = [start, length];
73     lines[lineCount_] = range;
74     lineCount_++;
75 }
76 /**
77  * Adds a line index to the end of <code>linesArray</code>.  Increases the
78  * size of the array if necessary and returns a new array.
79  * <p>
80  *
81  * @param start the start of the line
82  * @param length the length of the line
83  * @param linesArray the array to which to add the line index
84  * @param count the position at which to add the line
85  * @return a new array of line indexes
86  */
87 int[][] addLineIndex(int start, int length, int[][] linesArray, int count) {
88     int size = linesArray.length;
89     int[][] newLines = linesArray;
90     if (count is size) {
91         newLines = new int[][]( size+Compatibility.pow2(replaceExpandExp), 2 );
92         replaceExpandExp++;
93         System.arraycopy(linesArray, 0, newLines, 0, size);
94     }
95     int[] range = [start, length];
96     newLines[count] = range;
97     return newLines;
98 }
99 /**
100  * Adds a <code>TextChangeListener</code> listening for
101  * <code>TextChangingEvent</code> and <code>TextChangedEvent</code>. A
102  * <code>TextChangingEvent</code> is sent before changes to the text occur.
103  * A <code>TextChangedEvent</code> is sent after changes to the text
104  * occurred.
105  * <p>
106  *
107  * @param listener the listener
108  * @exception IllegalArgumentException <ul>
109  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
110  * </ul>
111  */
112 public void addTextChangeListener(TextChangeListener listener) {
113     if (listener is null) error(DWT.ERROR_NULL_ARGUMENT);
114     StyledTextListener typedListener = new StyledTextListener(listener);
115     textListeners ~= typedListener;
116 }
117 /**
118  * Adjusts the gap to accommodate a text change that is occurring.
119  * <p>
120  *
121  * @param position the position at which a change is occurring
122  * @param sizeHint the size of the change
123  * @param line the line where the gap will go
124  */
125 void adjustGap(int position, int sizeHint, int line) {
126     if (position is gapStart) {
127         // text is being inserted at the gap position
128         int size = (gapEnd - gapStart) - sizeHint;
129         if (lowWatermark <= size && size <= highWatermark)
130             return;
131     } else if ((position + sizeHint is gapStart) && (sizeHint < 0)) {
132         // text is being deleted at the gap position
133         int size = (gapEnd - gapStart) - sizeHint;
134         if (lowWatermark <= size && size <= highWatermark)
135             return;
136     }
137     moveAndResizeGap(position, sizeHint, line);
138 }
139 /**
140  * Calculates the indexes of each line in the text store.  Assumes no gap exists.
141  * Optimized to do less checking.
142  */
143 void indexLines(){
144     int start = 0;
145     lineCount_ = 0;
146     int textLength = textStore.length;
147     int i;
148     for (i = start; i < textLength; i++) {
149         char ch = textStore[i];
150         if (ch is DWT.CR) {
151             // see if the next character is a LF
152             if (i + 1 < textLength) {
153                 ch = textStore[i+1];
154                 if (ch is DWT.LF) {
155                     i++;
156                 }
157             }
158             addLineIndex(start, i - start + 1);
159             start = i + 1;
160         } else if (ch is DWT.LF) {
161             addLineIndex(start, i - start + 1);
162             start = i + 1;
163         }
164     }
165     addLineIndex(start, i - start);
166 }
167 /**
168  * Returns whether or not the given character is a line delimiter.  Both CR and LF
169  * are valid line delimiters.
170  * <p>
171  *
172  * @param ch the character to test
173  * @return true if ch is a delimiter, false otherwise
174  */
175 bool isDelimiter(char ch) {
176     if (ch is DWT.CR) return true;
177     if (ch is DWT.LF) return true;
178     return false;
179 }
180 /**
181  * Determine whether or not the replace operation is valid.  DefaultContent will not allow
182  * the /r/n line delimiter to be split or partially deleted.
183  * <p>
184  *
185  * @param start start offset of text to replace
186  * @param replaceLength start offset of text to replace
187  * @param newText start offset of text to replace
188  * @return a bool specifying whether or not the replace operation is valid
189  */
190 protected bool isValidReplace(int start, int replaceLength, String newText){
191     if (replaceLength is 0) {
192         // inserting text, see if the \r\n line delimiter is being split
193         if (start is 0) return true;
194         if (start is getCharCount()) return true;
195         char before = getTextRange(start - 1, 1)[0];
196         if (before is '\r') {
197             char after = getTextRange(start, 1)[0];
198             if (after is '\n') return false;
199         }
200     } else {
201         // deleting text, see if part of a \r\n line delimiter is being deleted
202         char startChar = getTextRange(start, 1)[0];
203         if (startChar is '\n') {
204             // see if char before delete position is \r
205             if (start !is 0) {
206                 char before = getTextRange(start - 1, 1)[0];
207                 if (before is '\r') return false;
208             }
209         }
210         char endChar = getTextRange(start + replaceLength - 1, 1)[0];
211         if (endChar is '\r') {
212             // see if char after delete position is \n
213             if (start + replaceLength !is getCharCount()) {
214                 char after = getTextRange(start + replaceLength, 1)[0];
215                 if (after is '\n') return false;
216             }
217         }
218     }
219     return true;
220 }
221 /**
222  * Calculates the indexes of each line of text in the given range.
223  * <p>
224  *
225  * @param offset the logical start offset of the text lineate
226  * @param length the length of the text to lineate, includes gap
227  * @param numLines the number of lines to initially allocate for the line index array,
228  *  passed in for efficiency (the exact number of lines may be known)
229  * @return a line indexes array where each line is identified by a start offset and
230  *  a length
231  */
232 int[][] indexLines(int offset, int length, int numLines){
233     int[][] indexedLines = new int[][]( numLines, 2 );
234     int start = 0;
235     int lineCount_ = 0;
236     int i;
237     replaceExpandExp = 1;
238     for (i = start; i < length; i++) {
239         int location = i + offset;
240         if ((location >= gapStart) && (location < gapEnd)) {
241             // ignore the gap
242         } else {
243             char ch = textStore[location];
244             if (ch is DWT.CR) {
245                 // see if the next character is a LF
246                 if (location+1 < textStore.length) {
247                     ch = textStore[location+1];
248                     if (ch is DWT.LF) {
249                         i++;
250                     }
251                 }
252                 indexedLines = addLineIndex(start, i - start + 1, indexedLines, lineCount_);
253                 lineCount_++;
254                 start = i + 1;
255             } else if (ch is DWT.LF) {
256                 indexedLines = addLineIndex(start, i - start + 1, indexedLines, lineCount_);
257                 lineCount_++;
258                 start = i + 1;
259             }
260         }
261     }
262     int[][] newLines = new int[][]( lineCount_+1, 2 );
263     System.arraycopy(indexedLines, 0, newLines, 0, lineCount_);
264     int[] range = [start, i - start];
265     newLines[lineCount_] = range;
266     return newLines;
267 }
268 /**
269  * Inserts text.
270  * <p>
271  *
272  * @param position the position at which to insert the text
273  * @param text the text to insert
274  */
275 void insert(int position, String text) {
276     if (text.length is 0) return;
277
278     int startLine = getLineAtOffset(position);
279     int change = text.length;
280     bool endInsert = position is getCharCount();
281     adjustGap(position, change, startLine);
282
283     // during an insert the gap will be adjusted to start at
284     // position and it will be associated with startline, the
285     // inserted text will be placed in the gap
286     int startLineOffset = getOffsetAtLine(startLine);
287     // at this point, startLineLength will include the start line
288     // and all of the newly inserted text
289     int startLineLength = getPhysicalLine(startLine).length;
290
291     if (change > 0) {
292         // shrink gap
293         gapStart += (change);
294         for (int i = 0; i < text.length; i++) {
295             textStore[position + i]= text[i];
296         }
297     }
298
299     // figure out the number of new lines that have been inserted
300     int [][] newLines = indexLines(startLineOffset, startLineLength, 10);
301     // only insert an empty line if it is the last line in the text
302     int numNewLines = newLines.length - 1;
303     if (newLines[numNewLines][1] is 0) {
304         // last inserted line is a new line
305         if (endInsert) {
306             // insert happening at end of the text, leave numNewLines as
307             // is since the last new line will not be concatenated with another
308             // line
309             numNewLines += 1;
310         } else {
311             numNewLines -= 1;
312         }
313     }
314
315     // make room for the new lines
316     expandLinesBy(numNewLines);
317     // shift down the lines after the replace line
318     for (int i = lineCount_ - 1; i > startLine; i--) {
319         lines[i + numNewLines]=lines[i];
320     }
321     // insert the new lines
322     for (int i = 0; i < numNewLines; i++) {
323         newLines[i][0] += startLineOffset;
324         lines[startLine + i]=newLines[i];
325     }
326     // update the last inserted line
327     if (numNewLines < newLines.length) {
328         newLines[numNewLines][0] += startLineOffset;
329         lines[startLine + numNewLines] = newLines[numNewLines];
330     }
331
332     lineCount_ += numNewLines;
333     gapLine = getLineAtPhysicalOffset(gapStart);
334 }
335 /**
336  * Moves the gap and adjusts its size in anticipation of a text change.
337  * The gap is resized to actual size + the specified size and moved to the given
338  * position.
339  * <p>
340  *
341  * @param position the position at which a change is occurring
342  * @param size the size of the change
343  * @param newGapLine the line where the gap should be put
344  */
345 void moveAndResizeGap(int position, int size, int newGapLine) {
346     char[] content = null;
347     int oldSize = gapEnd - gapStart;
348     int newSize;
349     if (size > 0) {
350         newSize = highWatermark + size;
351     } else {
352         newSize = lowWatermark - size;
353     }
354     // remove the old gap from the lines information
355     if (gapExists()) {
356         // adjust the line length
357         lines[gapLine][1] = lines[gapLine][1] - oldSize;
358         // adjust the offsets of the lines after the gapLine
359         for (int i = gapLine + 1; i < lineCount_; i++) {
360             lines[i][0] = lines[i][0] - oldSize;
361         }
362     }
363
364     if (newSize < 0) {
365         if (oldSize > 0) {
366             // removing the gap
367             content = new char[textStore.length - oldSize];
368             System.arraycopy(textStore, 0, content, 0, gapStart);
369             System.arraycopy(textStore, gapEnd, content, gapStart, content.length - gapStart);
370             textStore = content;
371         }
372         gapStart = gapEnd = position;
373         return;
374     }
375     content = new char[textStore.length + (newSize - oldSize)];
376     int newGapStart = position;
377     int newGapEnd = newGapStart + newSize;
378     if (oldSize is 0) {
379         System.arraycopy(textStore, 0, content, 0, newGapStart);
380         System.arraycopy(textStore, newGapStart, content, newGapEnd, content.length - newGapEnd);
381     } else if (newGapStart < gapStart) {
382         int delta = gapStart - newGapStart;
383         System.arraycopy(textStore, 0, content, 0, newGapStart);
384         System.arraycopy(textStore, newGapStart, content, newGapEnd, delta);
385         System.arraycopy(textStore, gapEnd, content, newGapEnd + delta, textStore.length - gapEnd);
386     } else {
387         int delta = newGapStart - gapStart;
388         System.arraycopy(textStore, 0, content, 0, gapStart);
389         System.arraycopy(textStore, gapEnd, content, gapStart, delta);
390         System.arraycopy(textStore, gapEnd + delta, content, newGapEnd, content.length - newGapEnd);
391     }
392     textStore = content;
393     gapStart = newGapStart;
394     gapEnd = newGapEnd;
395
396     // add the new gap to the lines information
397     if (gapExists()) {
398         gapLine = newGapLine;
399         // adjust the line length
400         int gapLength = gapEnd - gapStart;
401         lines[gapLine][1] = lines[gapLine][1] + (gapLength);
402         // adjust the offsets of the lines after the gapLine
403         for (int i = gapLine + 1; i < lineCount_; i++) {
404             lines[i][0] = lines[i][0] + gapLength;
405         }
406     }
407 }
408 /**
409  * Returns the number of lines that are in the specified text.
410  * <p>
411  *
412  * @param startOffset the start of the text to lineate
413  * @param length the length of the text to lineate
414  * @return number of lines
415  */
416 int lineCount(int startOffset, int length){
417     if (length is 0) {
418         return 0;
419     }
420     int lineCount_ = 0;
421     int count = 0;
422     int i = startOffset;
423     if (i >= gapStart) {
424         i += gapEnd - gapStart;
425     }
426     while (count < length) {
427         if ((i >= gapStart) && (i < gapEnd)) {
428             // ignore the gap
429         } else {
430             char ch = textStore[i];
431             if (ch is DWT.CR) {
432                 // see if the next character is a LF
433                 if (i + 1 < textStore.length) {
434                     ch = textStore[i+1];
435                     if (ch is DWT.LF) {
436                         i++;
437                         count++;
438                     }
439                 }
440                 lineCount_++;
441             } else if (ch is DWT.LF) {
442                 lineCount_++;
443             }
444             count++;
445         }
446         i++;
447     }
448     return lineCount_;
449 }
450 /**
451  * Returns the number of lines that are in the specified text.
452  * <p>
453  *
454  * @param text the text to lineate
455  * @return number of lines in the text
456  */
457 int lineCount(String text){
458     int lineCount_ = 0;
459     int length = text.length;
460     for (int i = 0; i < length; i++) {
461         char ch = text[i];
462         if (ch is DWT.CR) {
463             if (i + 1 < length && text[i + 1] is DWT.LF) {
464                 i++;
465             }
466             lineCount_++;
467         } else if (ch is DWT.LF) {
468             lineCount_++;
469         }
470     }
471     return lineCount_;
472 }
473 /**
474  * @return the logical length of the text store
475  */
476 public int getCharCount() {
477     int length = gapEnd - gapStart;
478     return (textStore.length - length);
479 }
480 /**
481  * Returns the line at <code>index</code> without delimiters.
482  * <p>
483  *
484  * @param index the index of the line to return
485  * @return the logical line text (i.e., without the gap)
486  * @exception IllegalArgumentException <ul>
487  *   <li>ERROR_INVALID_ARGUMENT when index is out of range</li>
488  * </ul>
489  */
490 public String getLine(int index) {
491     if ((index >= lineCount_) || (index < 0)) error(DWT.ERROR_INVALID_ARGUMENT);
492     int start = lines[index][0];
493     int length_ = lines[index][1];
494     int end = start + length_ - 1;
495     if (!gapExists() || (end < gapStart) || (start >= gapEnd)) {
496         // line is before or after the gap
497         while ((length_ - 1 >= 0) && isDelimiter(textStore[start+length_-1])) {
498             length_--;
499         }
500         return textStore[ start .. start + length_].dup;
501     } else {
502         // gap is in the specified range, strip out the gap
503         StringBuffer buf = new StringBuffer();
504         int gapLength = gapEnd - gapStart;
505         buf.append(textStore[ start .. gapStart ] );
506         buf.append(textStore[ gapEnd .. gapEnd + length_ - gapLength - (gapStart - start) ]);
507         length_ = buf.length;
508         while ((length_ - 1 >=0) && isDelimiter(buf.slice[length_ - 1])) {
509             length_--;
510         }
511         return buf.toString()[ 0 .. length_ ].dup;
512     }
513 }
514 /**
515  * Returns the line delimiter that should be used by the StyledText
516  * widget when inserting new lines.  This delimiter may be different than the
517  * delimiter that is used by the <code>StyledTextContent</code> interface.
518  * <p>
519  *
520  * @return the platform line delimiter as specified in the line.separator
521  *  system property.
522  */
523 public String getLineDelimiter() {
524     return LineDelimiter;
525 }
526 /**
527  * Returns the line at the given index with delimiters.
528  * <p>
529  * @param index the index of the line to return
530  * @return the logical line text (i.e., without the gap) with delimiters
531  */
532 String getFullLine(int index) {
533     int start = lines[index][0];
534     int length_ = lines[index][1];
535     int end = start + length_ - 1;
536     if (!gapExists() || (end < gapStart) || (start >= gapEnd)) {
537         // line is before or after the gap
538         return textStore[ start .. start + length_ ].dup;
539     } else {
540         // gap is in the specified range, strip out the gap
541         StringBuffer buffer = new StringBuffer();
542         int gapLength = gapEnd - gapStart;
543         buffer.append(textStore[ start .. gapStart ]);
544         buffer.append(textStore[ gapEnd .. gapEnd + length_ - gapLength - (gapStart - start) ]);
545         return buffer.toString().dup;
546     }
547 }
548 /**
549  * Returns the physical line at the given index (i.e., with delimiters and the gap).
550  * <p>
551  *
552  * @param index the line index
553  * @return the physical line
554  */
555 String getPhysicalLine(int index) {
556     int start = lines[index][0];
557     int length_ = lines[index][1];
558     return getPhysicalText(start, length_);
559 }
560 /**
561  * @return the number of lines in the text store
562  */
563 public int getLineCount(){
564     return lineCount_;
565 }
566 /**
567  * Returns the line at the given offset.
568  * <p>
569  *
570  * @param charPosition logical character offset (i.e., does not include gap)
571  * @return the line index
572  * @exception IllegalArgumentException <ul>
573  *    <li>ERROR_INVALID_ARGUMENT when charPosition is out of range</li>
574  * </ul>
575  */
576 public int getLineAtOffset(int charPosition){
577     if ((charPosition > getCharCount()) || (charPosition < 0)) error(DWT.ERROR_INVALID_ARGUMENT);
578     int position;
579     if (charPosition < gapStart) {
580         // position is before the gap