2 * (C) Copyright IBM Corp. 1998-2004. All Rights Reserved.
\r
4 * The program is provided "as is" without any warranty express or
\r
5 * implied, including the warranty of non-infringement and the implied
\r
6 * warranties of merchantibility and fitness for a particular purpose.
\r
7 * IBM will not be liable for any damages suffered by you as a result
\r
8 * of using the Program. In no event will IBM be liable for any
\r
9 * special, indirect or consequential damages or lost profits even if
\r
10 * IBM has been advised of the possibility of their occurrence. IBM
\r
11 * will not be liable for any third party claims against you.
\r
13 package com.ibm.richtext.textpanel;
\r
15 import java.awt.Adjustable;
\r
16 import java.awt.Component;
\r
17 import java.awt.datatransfer.Clipboard;
\r
19 import com.ibm.richtext.textlayout.attributes.AttributeMap;
\r
21 import com.ibm.richtext.styledtext.StyleModifier;
\r
22 import com.ibm.richtext.styledtext.MConstText;
\r
23 import com.ibm.richtext.styledtext.MText;
\r
24 import com.ibm.richtext.styledtext.StyledText;
\r
25 import com.ibm.richtext.textformat.TextOffset;
\r
28 * Implementation class for TextPanel and JTextPanel.
\r
30 final class ATextPanelImpl {
\r
32 static final String COPYRIGHT =
\r
33 "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
\r
35 private RunStrategy fRunStrategy = null;
\r
36 private TextComponent fTextComponent = null;
\r
37 private TextSelection fSelection = null;
\r
38 private TextEditBehavior fEditBehavior = null;
\r
39 private MText fText = null;
\r
41 private PanelEventBroadcaster fBroadcaster;
\r
42 private KeyRemap fRemap = KeyRemap.getIdentityRemap();
\r
44 // This is a little ugly. TextPanel supports its modified
\r
45 // flag whether or not it is editable, or even selectable.
\r
46 // So if there's no command log to keep track of the flag
\r
47 // state then its done right here in TextPanel. If the
\r
48 // panel is editable this flag is ignored.
\r
49 private boolean fModified = false;
\r
51 static final TextPanelSettings fgDefaultSettings = new TextPanelSettings();
\r
53 static TextPanelSettings getDefaultSettings() {
\r
55 return (TextPanelSettings) fgDefaultSettings.clone();
\r
58 ATextPanelImpl(RunStrategy runStrategy,
\r
59 TextPanelSettings settings,
\r
60 MConstText initialText,
\r
61 Clipboard clipboard,
\r
64 Adjustable vertSb) {
\r
66 fRunStrategy = runStrategy;
\r
67 fBroadcaster = new PanelEventBroadcaster(client);
\r
69 Scroller scroller = null;
\r
70 if (settings.getScrollable()) {
\r
71 scroller = new Scroller(horzSb, vertSb);
\r
74 StyledTextClipboard textClipboard =
\r
75 StyledTextClipboard.getClipboardFor(clipboard);
\r
77 fText = new StyledText();
\r
78 if (initialText != null) {
\r
79 fText.append(initialText);
\r
82 fTextComponent = new TextComponent(fText,
\r
83 settings.getDefaultValues(),
\r
84 settings.getWraps(),
\r
85 TextComponent.WINDOW_WIDTH,
\r
86 TextComponent.DEFAULT_INSET,
\r
88 settings.getScrollable(),
\r
92 if (scroller != null) {
\r
93 scroller.setClient(fTextComponent);
\r
96 // May have to wait until component has host to do this:
\r
97 if (settings.getSelectable()) {
\r
98 fSelection = new TextSelection(fTextComponent,
\r
101 fSelection.addToOwner(fTextComponent);
\r
102 if (settings.getEditable()) {
\r
103 fEditBehavior = new TextEditBehavior(
\r
104 fTextComponent, fSelection, fBroadcaster, fRemap);
\r
105 fEditBehavior.addToOwner(fTextComponent);
\r
110 FakeComponent getTextComponent() {
\r
112 return fTextComponent;
\r
116 * Add the given TextPanelListener to the listeners which will
\r
117 * receive update notifications from this TextPanel.
\r
118 * @param listener the listener to add
\r
120 public void addListener(TextPanelListener listener) {
\r
122 fBroadcaster.addListener(listener);
\r
126 * Remove the given TextPanelListener from the listeners which will
\r
127 * receive update notifications from this TextPanel.
\r
128 * @param listener the listener to remove
\r
130 public void removeListener(TextPanelListener listener) {
\r
132 fBroadcaster.removeListener(listener);
\r
136 * You know what this does...
\r
138 private static int pin(int value, int min, int max) {
\r
141 throw new IllegalArgumentException("Invalid range");
\r
147 else if (value > max) {
\r
158 * Set the document to <tt>newText</tt>. This operation
\r
159 * modifies the text in the TextPanel. It does not modify or adopt
\r
160 * <tt>newText</tt>. This method sets the selection an insertion point at
\r
161 * the end of the text.
\r
162 * @param newText the text which will replace the current text.
\r
164 public void setText(MConstText newText) {
\r
166 replaceRange(newText, 0, getTextLength());
\r
170 * Append the given text to the end of the document. Equivalent to
\r
171 * <tt>insert(newText, getTextLength())</tt>.
\r
172 * @param newText the text to append to the document
\r
174 public void append(MConstText newText) {
\r
176 int length = getTextLength();
\r
177 replaceRange(newText, length, length);
\r
181 * Insert the given text into the document at the given position.
\r
183 * <tt>replaceRange(newText, position, position)</tt>.
\r
184 * @param newText the text to insert into the document.
\r
185 * @param position the position in the document where the
\r
186 * text will be inserted
\r
188 public void insert(MConstText newText, int position) {
\r
190 replaceRange(newText, position, position);
\r
194 * Replace the given range with <tt>newText</tt>. After this
\r
195 * operation the selection range is an insertion point at the
\r
196 * end of the new text.
\r
197 * @param newText the text with which to replace the range
\r
198 * @param start the beginning of the range to replace
\r
199 * @param end the end of the range to replace
\r
201 public void replaceRange(MConstText newText, int start, int end) {
\r
203 int length = getTextLength();
\r
205 start = pin(start, 0, length);
\r
206 end = pin(end, start, length);
\r
208 if (fSelection != null) {
\r
210 // If we're selectable, but not editable, we'll temporarily
\r
211 // make ourselves editable to change the text. A little funny
\r
212 // but there's a lot of code for getting caret stuff right,
\r
213 // and this is not a common operation anyway.
\r
215 TextEditBehavior behavior;
\r
217 if (fEditBehavior == null) {
\r
218 behavior = new TextEditBehavior(fTextComponent, fSelection, fBroadcaster, fRemap);
\r
219 behavior.addToOwner(fTextComponent);
\r
222 behavior = fEditBehavior;
\r
225 TextOffset newSelection = new TextOffset(start + newText.length(),
\r
226 TextOffset.AFTER_OFFSET);
\r
228 TextReplacement replacement = new TextReplacement(start, end,
\r
233 fTextComponent.textControlEventOccurred(Behavior.REPLACE,
\r
235 if (fEditBehavior == null) {
\r
236 behavior.removeFromOwner();
\r
241 MText oldText = fTextComponent.getModifiableText();
\r
242 fTextComponent.stopBackgroundFormatting();
\r
243 oldText.replaceAll(newText);
\r
244 fTextComponent.reformatAndDrawText(0, newText.length(), null, null, null, null);
\r
249 * Return the length of the text document in the TextPanel.
\r
250 * @return the length of the text document in the TextPanel
\r
252 public int getTextLength() {
\r
254 return fTextComponent.getText().length();
\r
258 * Return the text document in the TextPanel.
\r
259 * @return the text document in the TextPanel.
\r
261 public MConstText getText() {
\r
263 return fTextComponent.getText();
\r
267 // Selection Access
\r
271 * Return the offset of the start of the selection.
\r
273 public int getSelectionStart() {
\r
275 if (fSelection != null) {
\r
276 return fSelection.getStart().fOffset;
\r
284 * Return the offset of the end of the selection.
\r
286 public int getSelectionEnd() {
\r
288 if (fSelection != null) {
\r
289 return fSelection.getEnd().fOffset;
\r
297 * Set the beginning of the selection range. This is
\r
298 * equivalent to <tt>select(selectionStart, getSelectionEnd())</tt>.
\r
299 * @param selectionStart the start of the new selection range
\r
301 public void setSelectionStart(int selectionStart) {
\r
303 select(selectionStart, getSelectionEnd());
\r
307 * Set the end of the selection range. This is
\r
308 * equivalent to <tt>select(getSelectionStart(), selectionEnd)</tt>.
\r
309 * @param selectionStart the start of the new selection range
\r
311 public void setSelectionEnd(int selectionEnd) {
\r
313 select(getSelectionStart(), selectionEnd);
\r
317 * Set the selection range to an insertion point at the given
\r
318 * offset. This is equivalent to
\r
319 * <tt>select(position, position)</tt>.
\r
320 * @param position the offset of the new insertion point
\r
322 public void setCaretPosition(int position) {
\r
324 select(position, position);
\r
328 * Set the selection range to the given range. The range start
\r
329 * is pinned between 0 and the text length; the range end is pinned
\r
330 * between the range start and the end of the text. These semantics
\r
331 * are identical to those of <tt>java.awt.TextComponent</tt>.
\r
332 * This method has no effect if the text is not selectable.
\r
333 * @param selectionStart the beginning of the selection range
\r
334 * @param selectionEnd the end of the selection range
\r
336 public void select(int selectionStart, int selectionEnd) {
\r
338 int length = getTextLength();
\r
340 selectionStart = pin(selectionStart, 0, length);
\r
341 selectionEnd = pin(selectionEnd, selectionStart, length);
\r
343 TextRange range = new TextRange(selectionStart, selectionEnd);
\r
344 fTextComponent.textControlEventOccurred(Behavior.SELECT, range);
\r
348 * Select all of the text in the document. This method has no effect if
\r
349 * the text is not selectable.
\r
351 public void selectAll() {
\r
353 select(0, getTextLength());
\r
362 * Return the total format width, in pixels. The format width is the
\r
363 * width to which text is wrapped.
\r
364 * @return the format width
\r
366 public int getFormatWidth() {
\r
368 return fTextComponent.getFormatWidth();
\r
372 * Return true if the paragraph at the given offset is left-to-right.
\r
373 * @param offset an offset in the text
\r
374 * @return true if the paragraph at the given offset is left-to-right
\r
376 public boolean paragraphIsLeftToRight(int offset) {
\r
378 return fTextComponent.paragraphIsLeftToRight(offset);
\r
382 * Return true if there is a change which can be undone.
\r
383 * @return true if there is a change which can be undone.
\r
385 public boolean canUndo() {
\r
387 if (fEditBehavior != null) {
\r
388 return fEditBehavior.canUndo();
\r
396 * Return true if there is a change which can be redone.
\r
397 * @return true if there is a change which can be redone.
\r
399 public boolean canRedo() {
\r
401 if (fEditBehavior != null) {
\r
402 return fEditBehavior.canRedo();
\r
410 * Return true if the clipboard contains contents which could be
\r
411 * transfered into the text.
\r
412 * @return true if the clipboard has text content.
\r
414 public boolean clipboardNotEmpty() {
\r
416 return fTextComponent.getClipboard().hasContents();
\r
420 * Return an AttributeMap of keys with default values. The default
\r
421 * values are used when displaying text for values which are not
\r
422 * specified in the text.
\r
423 * @return an AttributeMap of default key-value pairs
\r
425 public AttributeMap getDefaultValues() {
\r
427 return fTextComponent.getDefaultValues();
\r
430 private static boolean objectsAreEqual(Object lhs, Object rhs) {
\r
433 return rhs == null;
\r
436 return lhs.equals(rhs);
\r
440 private static Object consistentCharStyle(MConstText text,
\r
444 Object defaultValue) {
\r
446 if (start >= limit) {
\r
447 throw new IllegalArgumentException("Invalid range.");
\r
450 int runStart = start;
\r
451 Object initialValue = text.characterStyleAt(runStart).get(key);
\r
453 if (initialValue == null) {
\r
454 initialValue = defaultValue;
\r
457 for (runStart = text.characterStyleLimit(runStart);
\r
459 runStart = text.characterStyleLimit(runStart)) {
\r
461 Object nextValue = text.characterStyleAt(runStart).get(key);
\r
463 if (nextValue == null) {
\r
464 nextValue = defaultValue;
\r
467 if (!objectsAreEqual(initialValue, nextValue)) {
\r
468 return MTextPanel.MULTIPLE_VALUES;
\r
472 return initialValue;
\r
476 * This method inspects the character style runs in the selection
\r
477 * range (or the typing style at the insertion point) and returns:
\r
479 * <li>The value of <tt>key</tt>, if the value of <tt>key</tt>
\r
480 * is the same in all of the style runs in the selection, or</li>
\r
481 * <li>null, if two or more style runs have different values for <tt>key</tt>.</li>
\r
483 * If a style run does not contain <tt>key</tt>,
\r
484 * its value is considered to be <tt>defaultStyle</tt>.
\r
485 * This method is useful for configuring style menus.
\r
486 * @param key the key used to retrieve values for comparison
\r
487 * @param defaultValue the implicit value of <tt>key</tt> in
\r
488 * style runs where <tt>key</tt> is not defined
\r
490 public Object getCharacterStyleOverSelection(Object key) {
\r
492 TextRange selRange;
\r
493 if (fSelection != null)
\r
494 selRange = fSelection.getSelectionRange();
\r
496 selRange = new TextRange(0, 0);
\r
498 if (selRange.start == selRange.limit) {
\r
500 AttributeMap compStyle;
\r
502 if (fEditBehavior != null) {
\r
503 compStyle = fEditBehavior.getInsertionPointStyle();
\r
506 compStyle = TextEditBehavior.typingStyleAt(fText, selRange.start, selRange.limit);
\r
509 Object value = compStyle.get(key);
\r
510 return value==null? getDefaultValues().get(key) : value;
\r
513 return consistentCharStyle(fText,
\r
517 getDefaultValues().get(key));
\r
522 * This method inspects the paragraph style runs in the selection
\r
523 * range (or the typing style at the insertion point) and returns:
\r
525 * <li>The value of <tt>key</tt>, if the value of <tt>key</tt>
\r
526 * is the same in all of the style runs in the selection, or</li>
\r
527 * <li>null, if two or more style runs have different values for <tt>key</tt>.</li>
\r
529 * If a style run does not contain <tt>key</tt>,
\r
530 * its value is considered to be <tt>defaultStyle</tt>.
\r
531 * This method is useful for configuring style menus.
\r
532 * @param key the key used to retrieve values for comparison
\r
533 * @param defaultValue the implicit value of <tt>key</tt> in
\r
534 * style runs where <tt>key</tt> is not defined
\r
536 public Object getParagraphStyleOverSelection(Object key) {
\r
538 TextRange selRange;
\r
539 if (fSelection != null) {
\r
540 selRange = fSelection.getSelectionRange();
\r
543 selRange = new TextRange(0, 0);
\r
546 if (selRange.start == selRange.limit) {
\r
547 AttributeMap pStyle = fText.paragraphStyleAt(selRange.start);
\r
548 Object value = pStyle.get(key);
\r
549 return value==null? getDefaultValues().get(key) : value;
\r
552 int paragraphStart = selRange.start;
\r
553 Object defaultValue = getDefaultValues().get(key);
\r
554 Object initialValue = fText.paragraphStyleAt(paragraphStart).get(key);
\r
555 if (initialValue == null) {
\r
556 initialValue = defaultValue;
\r
559 for (paragraphStart = fText.paragraphLimit(paragraphStart);
\r
560 paragraphStart < selRange.limit;
\r
561 paragraphStart = fText.paragraphLimit(paragraphStart)) {
\r
563 Object nextValue = fText.paragraphStyleAt(paragraphStart).get(key);
\r
564 if (nextValue == null) {
\r
565 nextValue = defaultValue;
\r
568 if (!objectsAreEqual(initialValue, nextValue)) {
\r
569 return MTextPanel.MULTIPLE_VALUES;
\r
573 return initialValue;
\r
578 * Remove the selected text from the document and place it
\r
579 * on the clipboard. This method has no effect if the text
\r
580 * is not editable, or if no text is selected.
\r
582 public void cut() {
\r
583 fTextComponent.textControlEventOccurred(Behavior.CUT, null);
\r
587 * Place the selected text on the clipboard. This method has
\r
588 * no effect if no text is selected.
\r
590 public void copy() {
\r
591 fTextComponent.textControlEventOccurred(Behavior.COPY, null);
\r
595 * Replace the currently selected text with the text on the clipboard.
\r
596 * This method has no effect if the text is not editable, or if no
\r
597 * text is on the clipboard.
\r
599 public void paste() {
\r
600 fTextComponent.textControlEventOccurred(Behavior.PASTE, null);
\r
604 * Remove selected text from the document, without altering the clipboard.
\r
605 * This method has no effect if the
\r
606 * text is not editable.
\r
608 public void clear() {
\r
609 fTextComponent.textControlEventOccurred(Behavior.CLEAR, null);
\r
613 * Undo the most recent text change. This method has no effect if
\r
614 * there is no change to undo.
\r
616 public void undo() {
\r
617 fTextComponent.textControlEventOccurred(Behavior.UNDO, null);
\r
621 * Redo the most recent text change. This method has no effect if
\r
622 * there is no change to redo.
\r
624 public void redo() {
\r
625 fTextComponent.textControlEventOccurred(Behavior.REDO, null);
\r
629 * Return the number of commands the command log can hold.
\r
630 * @return the number of commands the command log can hold
\r
632 public int getCommandLogSize() {
\r
634 if (fEditBehavior != null) {
\r
635 return fEditBehavior.getCommandLogSize();
\r
643 * Set the number of commands the command log can hold. All
\r
644 * redoable commands are removed when this method is called.
\r
645 * @param size the number of commands kept in the command log
\r
647 public void setCommandLogSize(int size) {
\r
648 fTextComponent.textControlEventOccurred(Behavior.SET_COMMAND_LOG_SIZE,
\r
649 new Integer(size));
\r
653 * Remove all commands from the command log.
\r
655 public void clearCommandLog() {
\r
656 fTextComponent.textControlEventOccurred(Behavior.CLEAR_COMMAND_LOG, null);
\r
660 * Modify the character styles on the selected characters. If no characters
\r
661 * are selected, modify the typing style.
\r
662 * @param modifier the StyleModifier with which to modify the styles
\r
664 public void modifyCharacterStyleOnSelection(StyleModifier modifier) {
\r
665 fTextComponent.textControlEventOccurred(Behavior.CHARACTER_STYLE_MOD, modifier);
\r
669 * Modify the paragraph styles in paragraphs containing selected characters, or
\r
670 * the paragraph containing the insertion point.
\r
671 * @param modifier the StyleModifier with which to modify the styles
\r
673 public void modifyParagraphStyleOnSelection(StyleModifier modifier) {
\r
674 fTextComponent.textControlEventOccurred(Behavior.PARAGRAPH_STYLE_MOD, modifier);
\r
678 * Return the KeyRemap used to process key events.
\r
679 * @return the key remap used to process key events
\r
680 * @see #setKeyRemap
\r
682 public KeyRemap getKeyRemap() {
\r
688 * Use the given KeyRemap to map key events to characters.
\r
690 * events are affected by the remap; other text entering the
\r
691 * control (via the clipboard, for example) is not affected
\r
694 * Do not pass <tt>null</tt> to this method to leave key
\r
695 * events unmapped. Instead, use <tt>KeyRemap.getIdentityRemap()</tt>
\r
696 * @param remap the KeyRemap to use for mapping key events to characters
\r
697 * @exception java.lang.NullPointerException if parameter is null
\r
700 public void setKeyRemap(KeyRemap remap) {
\r
702 if (remap == null) {
\r
703 throw new NullPointerException("remap can't be null");
\r
707 if (fEditBehavior != null) {
\r
708 fEditBehavior.setKeyRemap(remap);
\r
711 fBroadcaster.textStateChanged(TextPanelEvent.KEYREMAP_CHANGED);
\r
715 * Return the modification flag of the current text change.
\r
716 * @see #setModified
\r
718 public boolean isModified() {
\r
720 if (fEditBehavior != null) {
\r
721 return fEditBehavior.isModified();
\r
729 * Set the modification flag of the current text change.
\r
731 public void setModified(boolean modified) {
\r
733 boolean handled = fTextComponent.textControlEventOccurred(
\r
734 Behavior.SET_MODIFIED,
\r
735 modified? Boolean.TRUE : Boolean.FALSE);
\r
737 fModified = modified;
\r
742 * This method is for perf-testing only!
\r
744 void handleKeyEvent(java.awt.event.KeyEvent keyEvent) {
\r
746 Component host = fTextComponent.getHost();
\r
747 if (host != null) {
\r
748 host.dispatchEvent(keyEvent);
\r