001: /*
002: * Copyright 2001-2006 C:1 Financial Services GmbH
003: *
004: * This software is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License Version 2.1, as published by the Free Software Foundation.
007: *
008: * This software is distributed in the hope that it will be useful,
009: * but WITHOUT ANY WARRANTY; without even the implied warranty of
010: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
011: * Lesser General Public License for more details.
012: *
013: * You should have received a copy of the GNU Lesser General Public
014: * License along with this library; if not, write to the Free Software
015: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
016: */
017:
018: package de.finix.contelligent.client.gui.text;
019:
020: import java.awt.BorderLayout;
021: import java.awt.Color;
022: import java.awt.event.ActionEvent;
023: import java.awt.event.FocusAdapter;
024: import java.awt.event.FocusEvent;
025: import java.awt.event.KeyAdapter;
026: import java.awt.event.KeyEvent;
027: import java.awt.event.MouseAdapter;
028: import java.awt.event.MouseEvent;
029: import java.io.IOException;
030: import java.io.Reader;
031: import java.io.StringReader;
032: import java.io.StringWriter;
033: import java.util.logging.Level;
034: import java.util.logging.Logger;
035:
036: import javax.swing.AbstractAction;
037: import javax.swing.Action;
038: import javax.swing.JPanel;
039: import javax.swing.JScrollPane;
040: import javax.swing.JTextPane;
041: import javax.swing.UIManager;
042: import javax.swing.event.CaretEvent;
043: import javax.swing.event.CaretListener;
044: import javax.swing.event.UndoableEditEvent;
045: import javax.swing.text.BadLocationException;
046: import javax.swing.text.Document;
047: import javax.swing.text.MutableAttributeSet;
048: import javax.swing.text.StyleConstants;
049: import javax.swing.undo.UndoManager;
050:
051: import de.finix.contelligent.client.base.ComponentFactory;
052: import de.finix.contelligent.client.base.ContelligentComponent;
053: import de.finix.contelligent.client.base.resource.ContelligentTextResource;
054: import de.finix.contelligent.client.event.ContelligentEvent;
055: import de.finix.contelligent.client.event.ResourceModifiedEvent;
056: import de.finix.contelligent.client.gui.AbstractComponentEditorWithExternal;
057: import de.finix.contelligent.client.gui.ContelligentAction;
058: import de.finix.contelligent.client.i18n.Resources;
059: import de.finix.contelligent.client.modules.preferences.PreferencesModule;
060: import de.finix.contelligent.client.util.ActionBundle;
061: import de.finix.contelligent.client.util.OptionalPopupMenu;
062:
063: public class TextEditor extends AbstractComponentEditorWithExternal {
064:
065: private static Logger logger = Logger.getLogger(TextEditor.class
066: .getName());
067:
068: private JTextPaneDND textEditor;
069:
070: private JScrollPane scrollPane = null;
071:
072: private ContelligentStyledDocument contelligentDocument;
073:
074: private OptionalPopupMenu popupMenu;
075:
076: private EditExternAction editExternAction = new EditExternAction();
077:
078: private UpdateFromExternAction updateFromExternAction = new UpdateFromExternAction();
079:
080: private SwitchRawTextViewAction switchRawTextViewAction = new SwitchRawTextViewAction();
081:
082: private int featureSet = ContelligentEditorKit.PLAIN_TEXT;
083:
084: private Color defaultForeground;
085:
086: private Action[] actions;
087:
088: private Action[] renderActions;
089:
090: private ContelligentEditorKit contelligentEditorKit;
091:
092: private static final int MAX_RESOURCE_UPDATE_LENGTH = 500;
093:
094: // FIXME only for experimental purposes
095: private static final boolean USE_RESOURCE_UPDATE = false;
096:
097: private String type;
098:
099: public void init() {
100: textEditor = new JTextPaneDND(this );
101:
102: popupMenu = new OptionalPopupMenu();
103: popupMenu.addActionBundleName(ActionBundle.EDITOR_ACTIONS);
104:
105: contelligentEditorKit = new ContelligentEditorKit(featureSet,
106: getView());
107: setResourceComponent(textEditor);
108: textEditor.setOpaque(true);
109: textEditor.setEditorKit(contelligentEditorKit);
110: contelligentEditorKit.setEditor(textEditor);
111: contelligentEditorKit.setTextEditor(this );
112:
113: // XXX: this is done double in init. updateActions() does the same.
114: Action[] kitActions = contelligentEditorKit
115: .getActions(getComponent());
116: actions = new Action[kitActions.length + 3];
117: System.arraycopy(kitActions, 0, actions, 0, kitActions.length);
118: actions[actions.length - 3] = switchRawTextViewAction;
119: actions[actions.length - 2] = editExternAction;
120: actions[actions.length - 1] = updateFromExternAction;
121:
122: if (contelligentEditorKit.isFeatureRichEditor()) {
123: renderActions = new Action[] { getAction("copy_action"),
124: getAction("find_action"),
125: getAction("find_again_action"),
126: switchRawTextViewAction };
127: } else {
128: renderActions = new Action[] { getAction("copy_action"),
129: getAction("find_action"),
130: getAction("find_again_action") };
131: }
132:
133: updateActions();
134:
135: contelligentDocument = getDocument(getComponent(), textEditor);
136: textEditor.setStyledDocument(contelligentDocument);
137: // register editorKit itself to update actions
138: final ContelligentEditorKit finalKit = contelligentEditorKit;
139: contelligentDocument.setUndoManager(new UndoManager() {
140: public void undoableEditHappened(UndoableEditEvent e) {
141: super .undoableEditHappened(e);
142: finalKit.updateUndoRedo();
143: }
144: });
145:
146: update();
147:
148: // We have to set up our layout in different ways, depending on whether
149: // lines should wrap.
150: if (PreferencesModule.getPreferences().getBoolean(
151: PreferencesModule.BREAK_LINES,
152: PreferencesModule.DEFAULT_BREAK_LINES)) {
153: scrollPane = new JScrollPane(textEditor);
154: } else {
155: // An intermediate panel with BorderLayout blocks visibility of the
156: // scrollpane size from the JTextPane, thus preventing line wrapping.
157: JPanel jp = new JPanel(new BorderLayout());
158: jp.add(textEditor);
159: scrollPane = new JScrollPane(jp);
160: }
161: add(scrollPane, BorderLayout.CENTER);
162:
163: textEditor.addFocusListener(new FocusAdapter() {
164: public void focusLost(FocusEvent e) {
165: logger.log(Level.FINE, "Text editor lost focus");
166: getView().getMenuAndToolbarComposer()
167: .removeOptionalPopupMenu(popupMenu);
168: }
169:
170: public void focusGained(FocusEvent e) {
171: logger.log(Level.FINE, "Text editor gained focus");
172: getView().getMenuAndToolbarComposer()
173: .addOptionalPopupMenu(popupMenu);
174: getView().setActions(getActions());
175: }
176: });
177:
178: textEditor.addKeyListener(new KeyAdapter() {
179: public void keyReleased(KeyEvent e) {
180: if (isEditable()) {
181: setResourceModified();
182: }
183: }
184: });
185:
186: textEditor.addMouseListener(new MouseAdapter() {
187: public void mouseReleased(MouseEvent e) {
188: if (e.isPopupTrigger() && isEditable()) {
189: showPopupMenu(e);
190: } else if (e.getClickCount() == 2) {
191: int clickOffset = textEditor.viewToModel(e
192: .getPoint());
193: if (clickOffset != -1) {
194: contelligentEditorKit.mousePressed(clickOffset);
195: }
196: }
197: }
198:
199: public void mousePressed(MouseEvent e) {
200: if (e.isPopupTrigger() && isEditable()) {
201: showPopupMenu(e);
202: }
203: }
204:
205: private void showPopupMenu(MouseEvent e) {
206: popupMenu.show(e.getComponent(), e.getX(), e.getY());
207: }
208: });
209:
210: if ((featureSet & ContelligentEditorKit.HANDLE_MARKUP) == ContelligentEditorKit.HANDLE_MARKUP) {
211:
212: // toggle buttons in dependancy of the style
213: textEditor.addCaretListener(new CaretListener() {
214:
215: public void caretUpdate(CaretEvent e) {
216: MutableAttributeSet caretAttributes = null;
217: Action normalText = null;
218: Action boldText = null;
219: Action italicText = null;
220: Action underlineText = null;
221: Action createLink = null;
222: Object bold = null;
223: Object italic = null;
224: Object underline = null;
225:
226: caretAttributes = contelligentEditorKit
227: .getInputAttributes();
228:
229: // this makes a greedy try to get actions ... though they
230: // may not exists, due to dependency on features
231: normalText = getAction("normal_action");
232: boldText = getAction("bold_action");
233: italicText = getAction("italic_action");
234: underlineText = getAction("underline_action");
235: createLink = getAction("link_action");
236:
237: if (normalText != null) {
238: normalText.putValue(ContelligentAction.STATE,
239: ContelligentAction.ON);
240: }
241:
242: if (boldText != null) {
243: bold = caretAttributes
244: .getAttribute(StyleConstants.CharacterConstants.Bold);
245: if (bold != null
246: && ((Boolean) bold).booleanValue()) {
247: normalText.putValue(
248: ContelligentAction.STATE,
249: ContelligentAction.OFF);
250: boldText.putValue(ContelligentAction.STATE,
251: ContelligentAction.ON);
252: } else {
253: boldText.putValue(ContelligentAction.STATE,
254: ContelligentAction.OFF);
255: }
256: }
257:
258: if (italicText != null) {
259: italic = caretAttributes
260: .getAttribute(StyleConstants.CharacterConstants.Italic);
261: if (italic != null
262: && ((Boolean) italic).booleanValue()) {
263: normalText.putValue(
264: ContelligentAction.STATE,
265: ContelligentAction.OFF);
266: italicText.putValue(
267: ContelligentAction.STATE,
268: ContelligentAction.ON);
269: } else {
270: italicText.putValue(
271: ContelligentAction.STATE,
272: ContelligentAction.OFF);
273: }
274: }
275:
276: if (underlineText != null) {
277: underline = caretAttributes
278: .getAttribute(StyleConstants.CharacterConstants.Underline);
279: if (underline != null
280: && ((Boolean) underline).booleanValue()) {
281: normalText.putValue(
282: ContelligentAction.STATE,
283: ContelligentAction.OFF);
284: underlineText.putValue(
285: ContelligentAction.STATE,
286: ContelligentAction.ON);
287: } else {
288: underlineText.putValue(
289: ContelligentAction.STATE,
290: ContelligentAction.OFF);
291: }
292: }
293: }
294: });
295: }
296: textEditor.setCaretPosition(0);
297: textEditor.setEditable(isEditable());
298:
299: logger
300: .log(Level.FINE, "initialized text editor: "
301: + textEditor);
302: }
303:
304: /**
305: * Overload for editor specific highlighting.
306: */
307: protected ContelligentStyledDocument getDocument(
308: ContelligentComponent component, JTextPane editorPane) {
309: return new ContelligentStyledDocument(component, editorPane);
310: }
311:
312: public ContelligentEditorKit getEditorKit() {
313: return contelligentEditorKit;
314: }
315:
316: public void setResourceModified() {
317: updateResource(USE_RESOURCE_UPDATE
318: && contelligentDocument.getLength() < MAX_RESOURCE_UPDATE_LENGTH);
319: update(false);
320: }
321:
322: protected void displayResource() {
323: ContelligentTextResource resource = (ContelligentTextResource) getGUI()
324: .getResource();
325: if (resource != null && resource.getText() != null) {
326: String text = resource.getText();
327: int caretPos = textEditor.getCaretPosition();
328: ContelligentStyledDocument doc = (ContelligentStyledDocument) textEditor
329: .getDocument();
330: try {
331: doc.removeForce(0, doc.getLength());
332: Reader r = new StringReader(text);
333: contelligentEditorKit.read(r, doc, 0);
334: } catch (IOException ioe) {
335: UIManager.getLookAndFeel().provideErrorFeedback(
336: textEditor);
337: } catch (BadLocationException ble) {
338: UIManager.getLookAndFeel().provideErrorFeedback(
339: textEditor);
340: }
341: // set caret position to avoid cursor repositioning after save
342: textEditor.setCaretPosition(Math.min(caretPos, doc
343: .getLength()));
344: } else {
345: textEditor.setText("");
346: }
347: contelligentDocument.getUndoManager().discardAllEdits();
348: contelligentEditorKit.updateUndoRedo();
349: }
350:
351: protected void initUndoManager() {
352: Document document = textEditor.getDocument();
353:
354: if (isEditable()
355: && document instanceof ContelligentStyledDocument) {
356: ((ContelligentStyledDocument) document).resetUndoManager();
357: contelligentEditorKit.updateUndoRedo();
358: }
359: }
360:
361: public void setEditable(boolean editable) {
362: super .setEditable(editable);
363: if (textEditor != null) {
364: textEditor.setEditable(editable);
365: initUndoManager();
366: }
367: }
368:
369: public void setFeatureSet(int featureSet) {
370: this .featureSet = featureSet;
371: }
372:
373: protected void updateActions() {
374: Action[] kitActions = contelligentEditorKit
375: .getActions(getComponent());
376: this .actions = new Action[kitActions.length + 3];
377: System.arraycopy(kitActions, 0, actions, 0, kitActions.length);
378: this .actions[actions.length - 3] = switchRawTextViewAction;
379: this .actions[actions.length - 2] = editExternAction;
380: this .actions[actions.length - 1] = updateFromExternAction;
381: // Dont set the actions on the view here anymore, since that
382: // would interfere with the automatic focus selection.
383: }
384:
385: public Action[] getActions() {
386: if (isEditable()) {
387: return actions;
388: } else {
389: return renderActions;
390: }
391: }
392:
393: protected void updateResource(boolean fireEvent) {
394: ContelligentTextResource resource = (ContelligentTextResource) getGUI()
395: .getResource();
396: if (resource == null || getGUI().isResourceInherited()) {
397: resource = new ContelligentTextResource(getGUI()
398: .getResourceCategoryMap(), textEditor.getText());
399: resource.setModified(true);
400: getGUI().setResource(resource);
401: } else {
402: resource.setText(textEditor.getText());
403: }
404: if (resource.isModified()) {
405: getComponent().setResourceModified(true);
406: }
407: if (fireEvent) {
408: ComponentFactory.getInstance()
409: .fireResourceModifyEvent(
410: new ResourceModifiedEvent(this ,
411: getComponent().getPath(), getGUI()
412: .getResourceIdentifier(),
413: getGUI().getResource(), getGUI()
414: .getResourceMode()));
415: }
416: }
417:
418: protected void updateComponent() {
419: ContelligentTextResource resource = (ContelligentTextResource) getGUI()
420: .getResource();
421: if (resource != null && !getGUI().isResourceInherited()) {
422: String text = textEditor.getText();
423: resource.setText(text);
424: if (resource.isModified()) {
425: getComponent().setResourceModified(true);
426: }
427: }
428: }
429:
430: protected void componentChanged(ContelligentEvent event) {
431: update();
432: }
433:
434: protected void childComponentAdded(ContelligentEvent event) {
435: }
436:
437: protected void childComponentRemoved(ContelligentEvent event) {
438: }
439:
440: protected void childComponentChanged(ContelligentEvent event) {
441: }
442:
443: protected void descendentComponentChanged(ContelligentEvent event) {
444: }
445:
446: protected String encodeTextForExternalEditing(String text) {
447: return text;
448: }
449:
450: protected String decodeTextFromExternalEditing(String text) {
451: return text;
452: }
453:
454: protected void notifyEditingFinished(String text, boolean successful) {
455: if (successful) {
456: ContelligentStyledDocument document = (ContelligentStyledDocument) textEditor
457: .getDocument();
458: try {
459: document.removeContent();
460: contelligentEditorKit.read(new StringReader(text),
461: document, 0);
462: setResourceModified();
463: } catch (Exception e) {
464: logger.log(Level.SEVERE, "Could not write into model",
465: e);
466: }
467: }
468: }
469:
470: private Action getAction(String name) {
471: for (int i = 0; i < actions.length; i++) {
472: if (actions[i].getValue(Action.NAME).equals(name)) {
473: return actions[i];
474: }
475: }
476: return null;
477: }
478:
479: public class SwitchRawTextViewAction extends AbstractAction
480: implements ContelligentAction {
481: public SwitchRawTextViewAction() {
482: super ("switch_raw_text_view_action");
483: putValue(Action.SMALL_ICON, Resources.toggleFinalIcon);
484: putValue(Action.SHORT_DESCRIPTION,
485: "switch_raw_text_view_description");
486: putValue(TYPE, PUSH_ACTION);
487: putValue(ACTION_TYPE, TEXT_ACTION);
488: putValue(ACTION_GROUP, TEXT_MARKUP_GROUP);
489: putValue(ACTION_POS, TEXT_MARKUP_TOGGLE);
490: putValue(MENU_TARGET, MENU);
491: putValue(BUTTON_TARGET, TOOLBAR);
492: }
493:
494: public void actionPerformed(ActionEvent event) {
495: setResourceModified();
496: if (contelligentEditorKit.isStateFeatureRich()) {
497: contelligentEditorKit.setStateFeatureRich(false);
498: // only update if really changed something
499: if (!contelligentEditorKit.isStateFeatureRich()) {
500: boolean textEditorHadFocus = false;
501: updateActions();
502: update();
503: // textEditor.requestFocus();
504: }
505: } else {
506: contelligentEditorKit.setStateFeatureRich(true);
507: // only update if really changed something
508: if (contelligentEditorKit.isStateFeatureRich()) {
509: updateActions();
510: update();
511: // textEditor.requestFocus();
512: }
513: }
514: }
515: }
516:
517: public class EditExternAction extends AbstractAction implements
518: ContelligentAction {
519:
520: public EditExternAction() {
521: super ("edit_extern_action");
522: putValue(Action.SMALL_ICON, Resources.launchEditorIcon);
523: putValue(ROLLOVER_ICON, Resources.launchEditorIconRollOver);
524: putValue(Action.SHORT_DESCRIPTION,
525: "edit_extern_action_description");
526: putValue(TYPE, PUSH_ACTION);
527: putValue(ACTION_TYPE, TEXT_ACTION);
528: putValue(ACTION_GROUP, TEXT_EXTERN_GROUP);
529: putValue(ACTION_POS, TEXT_EXTERN_EXTERN);
530: putValue(MENU_TARGET, MENU);
531: putValue(BUTTON_TARGET, TOOLBAR);
532: }
533:
534: public void actionPerformed(ActionEvent e) {
535: ContelligentStyledDocument document = (ContelligentStyledDocument) textEditor
536: .getDocument();
537: StringWriter stringWriter = new StringWriter();
538: try {
539: contelligentEditorKit.write(stringWriter, document, 0,
540: document.getLength());
541: startExternalEdit(stringWriter.toString());
542: } catch (Exception ex) {
543: logger.log(Level.SEVERE, "Could not load from model",
544: ex);
545: }
546: }
547: }
548:
549: public class UpdateFromExternAction extends AbstractAction
550: implements ContelligentAction {
551:
552: public UpdateFromExternAction() {
553: super ("update_extern_action");
554: putValue(Action.SMALL_ICON,
555: Resources.updateFromExternalIcon);
556: putValue(Action.SHORT_DESCRIPTION, "update_extern_action");
557: putValue(TYPE, PUSH_ACTION);
558: putValue(ACTION_TYPE, TEXT_ACTION);
559: putValue(ACTION_GROUP, TEXT_EXTERN_GROUP);
560: putValue(ACTION_POS, TEXT_EXTERN_UPDATE);
561: putValue(MENU_TARGET, MENU);
562: putValue(BUTTON_TARGET, TOOLBAR);
563: }
564:
565: public void actionPerformed(ActionEvent e) {
566: updateFromExternalEdit();
567: }
568: }
569:
570: public boolean isScalable() {
571: return true;
572: }
573:
574: /**
575: * @return String
576: */
577: public String getType() {
578: return type;
579: }
580:
581: /**
582: * Sets the type.
583: *
584: * @param type
585: * The type to set
586: */
587: public void setType(String type) {
588: this.type = type;
589: }
590:
591: }
|