001: /*
002: * (C) Copyright IBM Corp. 1998-2004. All Rights Reserved.
003: *
004: * The program is provided "as is" without any warranty express or
005: * implied, including the warranty of non-infringement and the implied
006: * warranties of merchantibility and fitness for a particular purpose.
007: * IBM will not be liable for any damages suffered by you as a result
008: * of using the Program. In no event will IBM be liable for any
009: * special, indirect or consequential damages or lost profits even if
010: * IBM has been advised of the possibility of their occurrence. IBM
011: * will not be liable for any third party claims against you.
012: */
013: package com.ibm.richtext.textpanel;
014:
015: import java.awt.Rectangle;
016:
017: import com.ibm.richtext.textlayout.attributes.AttributeMap;
018:
019: import java.awt.event.KeyEvent;
020: import java.awt.event.MouseEvent;
021:
022: import com.ibm.richtext.styledtext.MConstText;
023: import com.ibm.richtext.styledtext.MText;
024: import com.ibm.richtext.textformat.TextOffset;
025: import com.ibm.richtext.styledtext.StyleModifier;
026:
027: // All changes to the text should happen in this class, or in
028: // its TypingInteractor.
029:
030: class TextEditBehavior extends Behavior {
031:
032: static final String COPYRIGHT = "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
033: private TextComponent fTextComponent;
034: private TextSelection fSelection;
035: private MText fText;
036: private SimpleCommandLog fCommandLog;
037: private PanelEventBroadcaster fListener;
038: private TypingInteractor fTypingInteractor = null;
039: private KeyRemap fRemap;
040:
041: private AttributeMap fSavedTypingStyle = null;
042: private int fSavedInsPt = 0;
043:
044: public TextEditBehavior(TextComponent textComponent,
045: TextSelection selection, PanelEventBroadcaster listener,
046: KeyRemap remap) {
047:
048: fTextComponent = textComponent;
049: fSelection = selection;
050: fText = textComponent.getModifiableText();
051: fCommandLog = new SimpleCommandLog(listener);
052: fListener = listener;
053: fRemap = remap;
054: }
055:
056: public KeyRemap getKeyRemap() {
057:
058: return fRemap;
059: }
060:
061: public void setKeyRemap(KeyRemap remap) {
062:
063: fRemap = remap;
064: }
065:
066: public boolean textControlEventOccurred(Behavior.EventType event,
067: Object what) {
068:
069: boolean handled = true;
070:
071: if (event == Behavior.CHARACTER_STYLE_MOD
072: || event == Behavior.PARAGRAPH_STYLE_MOD) {
073: doStyleChange(event, what);
074: } else if (event == Behavior.CUT) {
075: doCut();
076: } else if (event == Behavior.PASTE) {
077: doPaste();
078: } else if (event == Behavior.CLEAR) {
079: doClear();
080: } else if (event == Behavior.REPLACE) {
081: doUndoableReplace((TextReplacement) what);
082: } else if (event == Behavior.UNDO) {
083: fCommandLog.undo();
084: } else if (event == Behavior.REDO) {
085: fCommandLog.redo();
086: } else if (event == Behavior.SET_MODIFIED) {
087: fCommandLog.setModified(what == Boolean.TRUE);
088: } else if (event == Behavior.CLEAR_COMMAND_LOG) {
089: fCommandLog.clearLog();
090: } else if (event == Behavior.SET_COMMAND_LOG_SIZE) {
091: fCommandLog.setLogSize(((Integer) what).intValue());
092: } else {
093: handled = super .textControlEventOccurred(event, what);
094: }
095:
096: checkSavedTypingStyle();
097:
098: return handled;
099: }
100:
101: /**
102: * It's unfortunate that the text is modified and reformatted in
103: * three different methods. This method is the "common prologue"
104: * for all text modifications.
105: *
106: * This method should be called before modifying and reformatting
107: * the text. It does three things: stops caret blinking, stops
108: * background formatting, and returns the Rectangle containing the
109: * current (soon-to-be obsolete) selection.
110: */
111: private Rectangle prepareForTextEdit() {
112:
113: fSelection.stopCaretBlinking();
114: fTextComponent.stopBackgroundFormatting();
115: return fTextComponent.getBoundingRect(fSelection.getStart(),
116: fSelection.getEnd());
117: }
118:
119: private void doClear() {
120: TextRange selRange = fSelection.getSelectionRange();
121:
122: if (selRange.start == selRange.limit)
123: return;
124:
125: doUndoableTextChange(selRange.start, selRange.limit, null,
126: new TextOffset(selRange.start), new TextOffset(
127: selRange.start));
128: }
129:
130: private void doCut() {
131: TextRange selRange = fSelection.getSelectionRange();
132:
133: if (selRange.start == selRange.limit)
134: return;
135:
136: fTextComponent.getClipboard().setContents(
137: fText.extract(selRange.start, selRange.limit));
138: doUndoableTextChange(selRange.start, selRange.limit, null,
139: new TextOffset(selRange.start), new TextOffset(
140: selRange.start));
141:
142: fListener.textStateChanged(TextPanelEvent.CLIPBOARD_CHANGED);
143: }
144:
145: private void doPaste() {
146: TextRange selRange = fSelection.getSelectionRange();
147: MConstText clipText = fTextComponent.getClipboard()
148: .getContents(AttributeMap.EMPTY_ATTRIBUTE_MAP);
149:
150: if (clipText != null) {
151: doUndoableTextChange(selRange.start, selRange.limit,
152: clipText, new TextOffset(selRange.start
153: + clipText.length()), new TextOffset(
154: selRange.start + clipText.length()));
155: } else {
156: fListener
157: .textStateChanged(TextPanelEvent.CLIPBOARD_CHANGED);
158: }
159: }
160:
161: private void doUndoableReplace(TextReplacement replacement) {
162:
163: doUndoableTextChange(replacement.getStart(), replacement
164: .getLimit(), replacement.getText(), replacement
165: .getSelectionStart(), replacement.getSelectionLimit());
166: }
167:
168: /**
169: * Only TypingInteractor and TextCommand should call this!
170: */
171: void doReplaceText(int start, int limit, MConstText newText,
172: TextOffset newSelStart, TextOffset newSelEnd) {
173:
174: int textLength;
175:
176: fText.resetDamagedRange();
177:
178: Rectangle oldSelRect = prepareForTextEdit();
179:
180: if (newText == null) {
181: textLength = 0;
182: fText.remove(start, limit);
183: } else {
184: textLength = newText.length();
185: fText.replace(start, limit, newText, 0, textLength);
186: }
187: fSelection.setSelectionRange(newSelStart, newSelEnd,
188: newSelStart);
189: reformatAndDrawText(fSelection.getStart(), fSelection.getEnd(),
190: oldSelRect);
191: }
192:
193: /**
194: * Only the typing interactor should call this!
195: */
196: void doReplaceSelectedText(char ch, AttributeMap charStyle) {
197:
198: int start = fSelection.getStart().fOffset;
199: int limit = fSelection.getEnd().fOffset;
200: TextOffset newOffset = new TextOffset(start + 1);
201: doReplaceText(start, limit, ch, charStyle, newOffset, newOffset);
202: }
203:
204: private void doReplaceText(int start, int limit, char ch,
205: AttributeMap charStyle, TextOffset newSelStart,
206: TextOffset newSelEnd) {
207:
208: fText.resetDamagedRange();
209:
210: Rectangle oldSelRect = prepareForTextEdit();
211:
212: fText.replace(start, limit, ch, charStyle);
213:
214: fSelection.setSelectionRange(newSelStart, newSelEnd,
215: newSelStart);
216: reformatAndDrawText(fSelection.getStart(), fSelection.getEnd(),
217: oldSelRect);
218: }
219:
220: private void doStyleChange(Behavior.EventType event, Object what) {
221:
222: TextRange selRange = fSelection.getSelectionRange();
223: boolean character = (event == Behavior.CHARACTER_STYLE_MOD);
224:
225: if (selRange.start != selRange.limit || !character) {
226: doUndoableStyleChange(what, character);
227: } else {
228: TypingInteractor interactor = new TypingInteractor(
229: fTextComponent, fSelection, fSavedTypingStyle,
230: this , fCommandLog, fListener);
231:
232: interactor.addToOwner(fTextComponent);
233: interactor.textControlEventOccurred(event, what);
234: }
235: }
236:
237: /**
238: * Only text commands should call this method!
239: */
240: void doModifyStyles(int start, int limit, StyleModifier modifier,
241: boolean character, TextOffset newSelStart,
242: TextOffset newSelEnd) {
243:
244: fText.resetDamagedRange();
245:
246: Rectangle oldSelRect = prepareForTextEdit();
247:
248: if (character) {
249: fText.modifyCharacterStyles(start, limit, modifier);
250: } else {
251: fText.modifyParagraphStyles(start, limit, modifier);
252: }
253:
254: fSelection.setSelectionRange(newSelStart, newSelEnd,
255: newSelStart);
256: reformatAndDrawText(newSelStart, newSelEnd, oldSelRect);
257: }
258:
259: private void doUndoableStyleChange(Object what, boolean character) {
260:
261: TextOffset selStart = fSelection.getStart();
262: TextOffset selEnd = fSelection.getEnd();
263:
264: MText oldText = fText.extractWritable(selStart.fOffset,
265: selEnd.fOffset);
266: StyleChangeCommand command = new StyleChangeCommand(this ,
267: oldText, selStart, selEnd, (StyleModifier) what,
268: character);
269:
270: fCommandLog.addAndDo(command);
271:
272: fListener
273: .textStateChanged(TextPanelEvent.SELECTION_STYLES_CHANGED);
274: }
275:
276: private void doUndoableTextChange(int start, int limit,
277: MConstText newText, TextOffset newSelStart,
278: TextOffset newSelEnd) {
279:
280: TextChangeCommand command = new TextChangeCommand(this , fText
281: .extractWritable(start, limit), newText, start,
282: fSelection.getStart(), fSelection.getEnd(),
283: newSelStart, newSelEnd);
284:
285: fCommandLog.addAndDo(command);
286: }
287:
288: public boolean canUndo() {
289:
290: boolean canUndo = false;
291:
292: if (fTypingInteractor != null) {
293: canUndo = fTypingInteractor.hasPendingCommand();
294: }
295:
296: if (!canUndo) {
297: canUndo = fCommandLog.canUndo();
298: }
299:
300: return canUndo;
301: }
302:
303: public boolean canRedo() {
304:
305: return fCommandLog.canRedo();
306: }
307:
308: public boolean isModified() {
309:
310: if (fTypingInteractor != null) {
311: if (fTypingInteractor.hasPendingCommand()) {
312: return true;
313: }
314: }
315: return fCommandLog.isModified();
316: }
317:
318: public int getCommandLogSize() {
319:
320: return fCommandLog.getLogSize();
321: }
322:
323: public AttributeMap getInsertionPointStyle() {
324:
325: if (fTypingInteractor != null) {
326: return fTypingInteractor.getTypingStyle();
327: }
328:
329: if (fSavedTypingStyle != null) {
330: return fSavedTypingStyle;
331: }
332:
333: TextRange range = fSelection.getSelectionRange();
334: return typingStyleAt(fText, range.start, range.limit);
335: }
336:
337: public boolean keyPressed(KeyEvent e) {
338:
339: boolean handled = true;
340: if (TypingInteractor.handledByTypingInteractor(e)) {
341: TypingInteractor interactor = new TypingInteractor(
342: fTextComponent, fSelection, fSavedTypingStyle,
343: this , fCommandLog, fListener);
344:
345: interactor.addToOwner(fTextComponent);
346: interactor.keyPressed(e);
347: } else {
348: handled = super .keyPressed(e);
349: checkSavedTypingStyle();
350: }
351:
352: return handled;
353: }
354:
355: public boolean keyTyped(KeyEvent e) {
356:
357: boolean handled = true;
358: if (TypingInteractor.handledByTypingInteractor(e)) {
359: TypingInteractor interactor = new TypingInteractor(
360: fTextComponent, fSelection, fSavedTypingStyle,
361: this , fCommandLog, fListener);
362:
363: interactor.addToOwner(fTextComponent);
364: interactor.keyTyped(e);
365: } else {
366: handled = super .keyTyped(e);
367: checkSavedTypingStyle();
368: }
369:
370: return handled;
371: }
372:
373: public boolean mouseReleased(MouseEvent e) {
374:
375: boolean result = super .mouseReleased(e);
376: checkSavedTypingStyle();
377: return result;
378: }
379:
380: private void reformatAndDrawText(TextOffset selStart,
381: TextOffset selLimit, Rectangle oldSelRect) {
382: if (!fSelection.enabled()) {
383: selStart = selLimit = null;
384: }
385:
386: int reformatStart = fText.damagedRangeStart();
387: int reformatLength = fText.damagedRangeLimit() - reformatStart;
388:
389: if (reformatStart != Integer.MAX_VALUE) {
390: fTextComponent.reformatAndDrawText(reformatStart,
391: reformatLength, selStart, selLimit, oldSelRect,
392: fSelection.getHighlightColor());
393: }
394:
395: fSelection.scrollToShowSelection();
396:
397: // sometimes this should send SELECTION_STYLES_CHANGED
398: fListener.textStateChanged(TextPanelEvent.TEXT_CHANGED);
399:
400: fSelection.restartCaretBlinking(true);
401: }
402:
403: /**
404: * Only TypingInteractor should call this.
405: */
406: void setTypingInteractor(TypingInteractor interactor) {
407: fTypingInteractor = interactor;
408: }
409:
410: /**
411: * Only TypingInteractor should call this.
412: */
413: void setSavedTypingStyle(AttributeMap style, int insPt) {
414:
415: fSavedTypingStyle = style;
416: fSavedInsPt = insPt;
417: }
418:
419: private void checkSavedTypingStyle() {
420:
421: if (fSavedTypingStyle != null) {
422: int selStart = fSelection.getStart().fOffset;
423: int selLimit = fSelection.getEnd().fOffset;
424: if (selStart != fSavedInsPt || selStart != selLimit) {
425: fSavedTypingStyle = null;
426: }
427: }
428: }
429:
430: /**
431: * Return the style appropriate for typing on the given selection
432: * range.
433: */
434: public static AttributeMap typingStyleAt(MConstText text,
435: int start, int limit) {
436:
437: if (start < limit) {
438: return text.characterStyleAt(start);
439: } else if (start > 0) {
440: return text.characterStyleAt(start - 1);
441: } else {
442: return text.characterStyleAt(0);
443: }
444: }
445: }
|