001: package com.jgraph.pad.graph;
002:
003: import java.awt.Component;
004: import java.awt.Container;
005: import java.awt.Dimension;
006: import java.awt.Font;
007: import java.awt.event.ActionEvent;
008: import java.awt.event.KeyEvent;
009: import java.awt.geom.Rectangle2D;
010: import java.util.EventObject;
011: import java.util.Map;
012:
013: import javax.swing.AbstractAction;
014: import javax.swing.AbstractCellEditor;
015: import javax.swing.JComponent;
016: import javax.swing.JLabel;
017: import javax.swing.JTextPane;
018: import javax.swing.KeyStroke;
019: import javax.swing.SwingUtilities;
020: import javax.swing.UIManager;
021: import javax.swing.event.DocumentEvent;
022: import javax.swing.event.DocumentListener;
023: import javax.swing.text.BadLocationException;
024: import javax.swing.text.Document;
025: import javax.swing.text.SimpleAttributeSet;
026: import javax.swing.text.StyleConstants;
027: import javax.swing.text.StyledDocument;
028:
029: import org.jgraph.JGraph;
030: import org.jgraph.graph.CellView;
031: import org.jgraph.graph.DefaultGraphCellEditor;
032: import org.jgraph.graph.GraphCellEditor;
033: import org.jgraph.graph.GraphConstants;
034:
035: /**
036: * In-place editor for rich text multiline values.
037: */
038: public class JGraphpadRichTextEditor extends DefaultGraphCellEditor {
039:
040: /**
041: * Constructs a new rich text editor.
042: */
043: public JGraphpadRichTextEditor() {
044: super ();
045: }
046:
047: /**
048: * Utlitiy editor for rich text values.
049: */
050: class RealCellEditor extends AbstractCellEditor implements
051: GraphCellEditor {
052:
053: /**
054: * Holds the component used for editing.
055: */
056: JTextPane editorComponent = new JTextPane();
057:
058: /**
059: * Constructs a new editor that supports shift- and control-enter
060: * keystrokes to insert newlines into the editing component.
061: */
062: public RealCellEditor() {
063: editorComponent.setBorder(UIManager
064: .getBorder("Tree.editorBorder"));
065: // editorComponent.setLineWrap(true);
066: // editorComponent.setWrapStyleWord(true);
067: // substitute a JTextArea's VK_ENTER action with our own that
068: // will stop an edit.
069: editorComponent.getInputMap(JComponent.WHEN_FOCUSED).put(
070: KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
071: "enter");
072: editorComponent.getInputMap(JComponent.WHEN_FOCUSED).put(
073: KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,
074: KeyEvent.SHIFT_DOWN_MASK), "metaEnter");
075: editorComponent.getInputMap(JComponent.WHEN_FOCUSED).put(
076: KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,
077: KeyEvent.CTRL_DOWN_MASK), "metaEnter");
078: editorComponent.getActionMap().put("enter",
079: new AbstractAction() {
080:
081: public void actionPerformed(ActionEvent e) {
082: stopCellEditing();
083: }
084: });
085: editorComponent.getActionMap().put("metaEnter",
086: new AbstractAction() {
087:
088: public void actionPerformed(ActionEvent e) {
089: Document doc = editorComponent
090: .getDocument();
091: try {
092: doc
093: .insertString(editorComponent
094: .getCaretPosition(),
095: "\n", null);
096: } catch (BadLocationException e1) {
097: e1.printStackTrace();
098: }
099: }
100: });
101: }
102:
103: /**
104: * Sets of the editor for editing the rich text value.
105: */
106: public Component getGraphCellEditorComponent(JGraph graph,
107: Object value, boolean isSelected) {
108: Rectangle2D cellBounds = graph.getCellBounds(value);
109: if (cellBounds != null) {
110: Dimension maxSize = new Dimension((int) cellBounds
111: .getWidth(), (int) cellBounds.getHeight());
112: editorComponent.setMaximumSize(maxSize);
113: }
114: Object cell = value;
115: value = graph.getModel().getValue(value);
116: if (value instanceof JGraphpadBusinessObject
117: && ((JGraphpadBusinessObject) value).isRichText())
118: try {
119: StyledDocument document = (StyledDocument) editorComponent
120: .getDocument();
121:
122: ((JGraphpadRichTextValue) ((JGraphpadBusinessObject) value)
123: .getValue()).insertInto(document);
124:
125: // Workaround for trailing newline
126: if (document.getLength() > 0)
127: document.remove(document.getLength() - 1, 1);
128:
129: // Workaround for the alignment not being serializable:
130: // We use the label's alignment as the global alignment
131: CellView view = graph.getGraphLayoutCache()
132: .getMapping(cell, false);
133: if (view != null) {
134: Map map = view.getAllAttributes();
135: int align = GraphConstants
136: .getHorizontalAlignment(map);
137: SimpleAttributeSet sas = new SimpleAttributeSet();
138: align = (align == JLabel.CENTER) ? StyleConstants.ALIGN_CENTER
139: : (align == JLabel.RIGHT) ? StyleConstants.ALIGN_RIGHT
140: : StyleConstants.ALIGN_LEFT;
141: StyleConstants.setAlignment(sas, align);
142: document.setParagraphAttributes(0, document
143: .getLength(), sas, true);
144: }
145: } catch (Exception e) {
146: e.printStackTrace();
147: }
148: else {
149: editorComponent.setText(value.toString());
150: }
151: editorComponent.selectAll();
152: editorComponent.getDocument().addDocumentListener(
153: new DocumentListener() {
154:
155: public void updateSize() {
156: SwingUtilities.invokeLater(new Runnable() {
157:
158: public void run() {
159: Container container = editorComponent
160: .getParent();
161: if (container != null) {
162: container.doLayout();
163: container
164: .setSize(editingContainer
165: .getPreferredSize());
166: container.invalidate();
167: }
168: }
169: });
170: }
171:
172: public void insertUpdate(DocumentEvent arg0) {
173: updateSize();
174: }
175:
176: public void removeUpdate(DocumentEvent arg0) {
177: updateSize();
178: }
179:
180: public void changedUpdate(DocumentEvent arg0) {
181: updateSize();
182: }
183: });
184: return editorComponent;
185: }
186:
187: /**
188: * Returns the rich text value to be stored in the user object.
189: */
190: public Object getCellEditorValue() {
191: return new JGraphpadRichTextValue(editorComponent
192: .getDocument());
193: }
194:
195: /**
196: * Transfers the focus to the editing component.
197: */
198: public boolean shouldSelectCell(EventObject event) {
199: editorComponent.requestFocus();
200: return super .shouldSelectCell(event);
201: }
202: }
203:
204: /**
205: * Overriding this in order to set the size of an editor to that of an
206: * edited view.
207: */
208: public Component getGraphCellEditorComponent(JGraph graph,
209: Object cell, boolean isSelected) {
210: Component component = super .getGraphCellEditorComponent(graph,
211: cell, isSelected);
212: // set the size of an editor to that of the view
213: CellView view = graph.getGraphLayoutCache().getMapping(cell,
214: false);
215: Rectangle2D tmp = view.getBounds();
216: editingComponent.setBounds((int) tmp.getX(), (int) tmp.getY(),
217: (int) tmp.getWidth(), (int) tmp.getHeight());
218: // I have to set a font here instead of in the
219: // RealCellEditor.getGraphCellEditorComponent() because
220: // I don't know what cell is being edited when in the
221: // RealCellEditor.getGraphCellEditorComponent().
222: Font font = GraphConstants.getFont(view.getAllAttributes());
223: editingComponent.setFont((font != null) ? font : graph
224: .getFont());
225: return component;
226: }
227:
228: /**
229: * Returns a new RealCellEditor.
230: */
231: protected GraphCellEditor createGraphCellEditor() {
232: return new JGraphpadRichTextEditor.RealCellEditor();
233: }
234:
235: /**
236: * Overriting this so that I could modify an editor container. See also
237: * <A
238: * HREF="http://sourceforge.net/forum/forum.php?thread_id=781479&forum_id=140880">here</A>.
239: */
240: protected Container createContainer() {
241: return new JGraphpadRichTextEditor.ModifiedEditorContainer();
242: }
243:
244: /**
245: * Utitlitiy container with a custom layout.
246: */
247: class ModifiedEditorContainer extends EditorContainer {
248:
249: /**
250: * Performs the layout of the components in the container.
251: */
252: public void doLayout() {
253: super .doLayout();
254: // substract 2 pixels that were added to the preferred size of
255: // the container for the border.
256: Dimension cSize = getSize();
257: Dimension dim = editingComponent.getSize();
258: editingComponent.setSize(dim.width - 2, dim.height);
259: // reset container's size based on a potentially new preferred
260: // size of a real editor.
261: setSize(cSize.width, getPreferredSize().height);
262: }
263: }
264: }
|