001: /*
002: * Copyright (c) 2000, Jacob Smullyan.
003: *
004: * This is part of SkunkDAV, a WebDAV client. See http://skunkdav.sourceforge.net/
005: * for the latest version.
006: *
007: * SkunkDAV is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License as published
009: * by the Free Software Foundation; either version 2, or (at your option)
010: * any later version.
011: *
012: * SkunkDAV is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with SkunkDAV; see the file COPYING. If not, write to the Free
019: * Software Foundation, 59 Temple Place - Suite 330, Boston, MA
020: * 02111-1307, USA.
021: */
022:
023: package org.skunk.dav.client.gui.editor;
024:
025: import java.awt.Color;
026: import java.awt.Component;
027: import java.awt.Dimension;
028: import java.awt.Font;
029: import java.awt.Insets;
030: import java.awt.Rectangle;
031: import java.util.ArrayList;
032: import javax.swing.JComponent;
033: import javax.swing.JEditorPane;
034: import javax.swing.JScrollPane;
035: import javax.swing.event.CaretEvent;
036: import javax.swing.event.CaretListener;
037: import javax.swing.event.DocumentEvent;
038: import javax.swing.event.DocumentListener;
039: import javax.swing.text.BadLocationException;
040: import javax.swing.text.Caret;
041: import javax.swing.text.Document;
042: import javax.swing.text.Element;
043: import org.skunk.dav.client.DAVFile;
044: import org.skunk.dav.client.gui.AppContext;
045: import org.skunk.dav.client.gui.Buffer;
046: import org.skunk.dav.client.gui.BusinessTracker;
047: import org.skunk.dav.client.gui.Explorer;
048: import org.skunk.dav.client.gui.ExplorerApp;
049: import org.skunk.dav.client.gui.StateMonitor;
050: import org.skunk.dav.client.gui.StateProperties;
051: import org.skunk.dav.client.gui.TextPoint;
052: import org.skunk.dav.client.gui.View;
053: import org.skunk.swing.text.TextEditorPane;
054: import org.skunk.swing.text.syntax.FileMode;
055: import org.skunk.swing.text.syntax.SyntaxDocument;
056: import org.skunk.swing.text.syntax.SyntaxEditorKit;
057: import org.skunk.swing.text.syntax.SyntaxStyle;
058: import org.skunk.trace.Debug;
059:
060: /**
061: * text editor supporting search, navigation, and syntax highlighting.
062: */
063: public class SimpleTextEditor extends AbstractDAVEditor implements
064: ConfigurableEditor {
065: private TextEditorPane editingPane;
066: private JScrollPane edcomp;
067: private DAVEditorUndoManager undoManager;
068: private View currentView;
069: private int tabSize;
070:
071: public SimpleTextEditor(DAVFile file) {
072: super (file);
073: editingPane = new TextEditorPane();
074: editingPane.setEditorKit(new SyntaxEditorKit());
075: /* I am commenting this out, because the corresponding code in the Flexicizer introduces an
076: undesirable dependency into the build */
077: // if (Debug.DEBUG && Debug.isDebug(org.skunk.swing.text.syntax.SyntaxDocument.class, Debug.DP8))
078: // {
079: // getSyntaxDocument().putProperty("editor", this);
080: // }
081: //defaults of configurable properties
082: editingPane.setFont(SyntaxStyle.getDefaultFont());
083: setMargin(new Insets(15, 15, 15, 30));
084: setBackground(SyntaxStyle.DEFAULT_BACKGROUND);
085: setForeground(SyntaxStyle.DEFAULT_FOREGROUND);
086: setCaretColor(DEFAULT_CARET_COLOR);
087: setTokenizing(false);
088: setTabSize(DEFAULT_TAB_SIZE);
089:
090: KeyMapper.loadKeymap(editingPane);
091: edcomp = new JScrollPane(editingPane);
092: undoManager = new DAVEditorUndoManager();
093: initListeners();
094:
095: }
096:
097: public Insets getMargin() {
098: return editingPane.getMargin();
099: }
100:
101: public void setMargin(Insets margin) {
102: editingPane.setMargin(margin);
103: }
104:
105: public Color getBackground() {
106: return editingPane.getBackground();
107: }
108:
109: public void setBackground(Color background) {
110: if (Debug.DEBUG)
111: Debug.trace(this , Debug.DP4, "in setBackground({0}",
112: background);
113: editingPane.setBackground(background);
114: editingPane.repaint();
115: }
116:
117: public Color getForeground() {
118: return editingPane.getForeground();
119: }
120:
121: public void setForeground(Color foreground) {
122: editingPane.setForeground(foreground);
123: }
124:
125: public Color getCaretColor() {
126: return editingPane.getCaretColor();
127: }
128:
129: public void setCaretColor(Color caretColor) {
130: editingPane.setCaretColor(caretColor);
131: }
132:
133: public void setFont(Font defaultFont) {
134: editingPane.setFont(defaultFont);
135: }
136:
137: public int getTabSize() {
138: return this .tabSize;
139: }
140:
141: public void setTabSize(int tabSize) {
142: this .tabSize = tabSize;
143: //undocumented, but this is how documents store the tab size.
144: editingPane.getDocument().putProperty("tabSize",
145: new Integer(tabSize));
146: }
147:
148: public boolean isTokenizing() {
149: return getSyntaxDocument().isTokenizing();
150: }
151:
152: public void setTokenizing(boolean tokenize) {
153: getSyntaxDocument().setTokenizing(tokenize);
154: }
155:
156: public FileMode getFileMode() {
157: return getSyntaxDocument().getFileMode();
158: }
159:
160: public void setFileMode(FileMode mode) {
161: getSyntaxDocument().setFileMode(mode);
162: if (mode == null || !mode.getCanHighlight())
163: setTokenizing(false);
164: else
165: setTokenizing(mode.getShouldHighlight());
166: }
167:
168: public boolean isWordWrap() {
169: return this .editingPane.getWordWrap();
170: }
171:
172: public void setWordWrap(boolean wordWrap) {
173: this .editingPane.setWordWrap(wordWrap);
174: this .editingPane.revalidate();
175: }
176:
177: private void initListeners() {
178: editingPane.getDocument().addDocumentListener(
179: new DocumentListener() {
180: public void changedUpdate(DocumentEvent dee) {
181: //do nothing
182: }
183:
184: public void insertUpdate(DocumentEvent dee) {
185: setDirty(true);
186: }
187:
188: public void removeUpdate(DocumentEvent dee) {
189: setDirty(true);
190: }
191: });
192: editingPane.addCaretListener(new CaretListener() {
193: public void caretUpdate(CaretEvent caries) {
194: publishCaretUpdate(caries.getDot());
195: }
196: });
197:
198: editingPane.getDocument().addUndoableEditListener(undoManager);
199:
200: StateMonitor.registerProperty(this , "editorInFocus",
201: StateProperties.EDITOR_IN_FOCUS, null, null);
202: }
203:
204: public void setDirty(boolean dirty) {
205: super .setDirty(dirty);
206: if (!dirty && undoManager != null && undoManager.canUndo())
207: undoManager.discardAllEdits();
208: }
209:
210: public DAVEditorUndoManager getUndoManager() {
211: return undoManager;
212: }
213:
214: private void publishCaretUpdate(int dot) {
215: TextPoint tp = getLineAndColumnForOffset(dot);
216: Object o = (tp == null) ? (Object) "anything" : tp;
217: StateMonitor.setProperty(StateProperties.CARET_POSITION, o,
218: this .currentView);
219: }
220:
221: public void setEditorInFocus(boolean focussed) {
222: if (focussed) {
223: View v = ExplorerApp.getViewForBuffer(this );
224: if (v == null) {
225: if (Debug.DEBUG)
226: Debug.trace(this , Debug.DP3,
227: "view for this buffer is null");
228: return;
229: }
230: this .currentView = v;
231: Buffer b = v.getFocussedBuffer();
232: if (b != null && this .equals(b)) {
233: publishCaretUpdate(editingPane.getCaret().getDot());
234: }
235: //if another text editor has focus, do nothing, it will take care of it
236: else if (!(b instanceof SimpleTextEditor))
237: publishCaretUpdate(-1);
238: }
239: // multiple text editors may do this redundantly
240: else
241: publishCaretUpdate(-1);
242: }
243:
244: public void copy() {
245: editingPane.copy();
246: }
247:
248: public void cut() {
249: editingPane.cut();
250: }
251:
252: public void paste() {
253: editingPane.paste();
254: }
255:
256: public void replaceSelection(String content) {
257: getUndoManager().beginUpdate();
258: editingPane.replaceSelection(content);
259: getUndoManager().endUpdate();
260: }
261:
262: private int getLineCount() {
263: Document doc = editingPane.getDocument();
264: if (doc == null)
265: return 0;
266: Element mainElem = doc.getDefaultRootElement();
267: if (mainElem == null)
268: return 0;
269: return mainElem.getElementCount();
270: }
271:
272: private int getLineForOffset(int offset) {
273: Document doc = editingPane.getDocument();
274: if (doc == null)
275: return -1;
276: Element mainElem = doc.getDefaultRootElement();
277: if (mainElem == null)
278: return -1;
279: return mainElem.getElementIndex(offset);
280: }
281:
282: private int getOffsetForLine(int line) {
283: Document doc = editingPane.getDocument();
284: if (doc == null)
285: return -1;
286: Element mainElem = doc.getDefaultRootElement();
287: Element lineElem = mainElem.getElement(line);
288: return lineElem.getStartOffset();
289: }
290:
291: private TextPoint getLineAndColumnForOffset(int offset) {
292: if (offset < 0)
293: return null;
294: Document doc = editingPane.getDocument();
295: if (doc == null)
296: return null;
297: Element mainElem = doc.getDefaultRootElement();
298: if (mainElem == null)
299: return null;
300: int x = mainElem.getElementIndex(offset);
301: int y = offset - mainElem.getElement(x).getStartOffset();
302: return new TextPoint(x, y);
303: }
304:
305: public void select(int dot, int mark) {
306: editingPane.setCaretPosition(dot);
307: editingPane.moveCaretPosition(mark);
308: //on Windows sometimes the selection is not highlighted;
309: //this is an attempt to fix that
310: editingPane.getCaret().setSelectionVisible(true);
311: scrollTo(mark);
312: }
313:
314: public void selectAll() {
315: Document doc = editingPane.getDocument();
316: int dot = 0;
317: int mark = doc.getLength();
318: select(dot, mark);
319: }
320:
321: public void gotoLine(int line) {
322: if (line < 0)
323: line = 0;
324: int max = getLineCount();
325: if (max > 0)
326: max--;
327: if (line > max)
328: line = max;
329: int offset = getOffsetForLine(line);
330: if (offset != -1) {
331: editingPane.setCaretPosition(offset);
332: scrollTo(offset);
333: }
334: }
335:
336: public void scrollTo(int offset) {
337: try {
338: Rectangle r = editingPane.modelToView(offset);
339: if (r != null)
340: editingPane.scrollRectToVisible(r);
341: editingPane.getCaret().setVisible(true);
342: } catch (BadLocationException badLuck) {
343: if (Debug.DEBUG)
344: Debug.trace(this , Debug.DP2, badLuck);
345: }
346: }
347:
348: public SyntaxDocument getSyntaxDocument() {
349: return (SyntaxDocument) editingPane.getDocument();
350: }
351:
352: public Document getDocument() {
353: return editingPane.getDocument();
354: }
355:
356: public Caret getCaret() {
357: return editingPane.getCaret();
358: }
359:
360: public JComponent getComponent() {
361: return edcomp;
362: }
363:
364: public void setWriteable(boolean writeable) {
365: super .setWriteable(writeable);
366: editingPane.setEditable(writeable);
367: }
368:
369: public void save() {
370: BusinessTracker.addBusy(this );
371: setResourceBody(editingPane.getText().getBytes());
372: super .save();
373: undoManager.discardAllEdits();
374: BusinessTracker.removeBusy(this );
375: }
376:
377: public void load() {
378: BusinessTracker.addBusy(this );
379: //determine FileMode
380: FileMode fm = FileMode.getModeForFilename(getResourceName());
381:
382: if (fm != null) {
383: setFileMode(fm);
384: }
385:
386: byte[] body = getResourceBody();
387: if (body != null) {
388: boolean savedTokenizationState = isTokenizing();
389: if (savedTokenizationState)
390: setTokenizing(false);
391: editingPane.setText(new String(body));
392: if (savedTokenizationState) {
393: setTokenizing(true);
394: getSyntaxDocument().retokenizeAll();
395: }
396: }
397: scrollTo(0);
398: editingPane.requestFocus();
399: editingPane.setCaretPosition(0);
400: editingPane.getCaret().setVisible(true);
401: undoManager.discardAllEdits();
402: setDirty(false);
403: BusinessTracker.removeBusy(this );
404: }
405:
406: }
407:
408: /* $Log: SimpleTextEditor.java,v $
409: /* Revision 1.28 2001/02/16 18:15:10 smulloni
410: /* many fixes to TextEditorCustomizer. FileMode and SyntaxStyle now have a
411: /* configData property (they will probably be made into sibling classes).
412: /*
413: /* Revision 1.27 2001/02/13 22:53:50 smulloni
414: /* adding a syntax highlighting mode for STML (Skunk Template Markup Language);
415: /* fixed a bug in SyntaxStyle in reading default.styles.
416: /*
417: /* Revision 1.26 2001/02/09 20:00:00 smulloni
418: /* fixed particularly nasty bug in GappedIntArray.set(int, int[]), and other
419: /* bugs in the syntax highlighting system.
420: /*
421: /* Revision 1.25 2001/02/06 23:15:53 smulloni
422: /* added syntax highlight and selectAll actions to the menu
423: /*
424: /* Revision 1.24 2001/02/06 22:13:41 smulloni
425: /* first more-or-less working version of syntax highlighting, with customization.
426: /*
427: /* Revision 1.23 2001/02/06 00:11:18 smulloni
428: /* struggle, perhaps futile, with the TextEditorCustomizer and other implicated
429: /* classes
430: /*
431: /* Revision 1.22 2001/02/02 23:30:33 smulloni
432: /* adding customization features to the text editor.
433: /*
434: /* Revision 1.21 2001/01/30 23:03:19 smulloni
435: /* beginning of integration of syntax highlighting into SimpleTextEditor.
436: /*
437: /* Revision 1.20 2000/12/21 18:53:13 smulloni
438: /* cosmetic improvements.
439: /*
440: /* Revision 1.19 2000/12/19 22:36:05 smulloni
441: /* adjustments to preamble.
442: /*
443: /* Revision 1.18 2000/12/15 23:22:08 smulloni
444: /* added word wrap to text editor.
445: /*
446: /* Revision 1.17 2000/12/14 23:09:17 smulloni
447: /* fixes to search and replace; partial fix to adding custom properties bug.
448: /*
449: /* Revision 1.16 2000/12/14 06:36:26 smulloni
450: /* changes to search and replace in text editor.
451: /*
452: /* Revision 1.15 2000/12/13 17:59:50 smulloni
453: /* added replaceSelection(String s)
454: /*
455: /* Revision 1.14 2000/12/08 16:36:48 smulloni
456: /* fixed bug that didn't update the Location field correctly when switching
457: /* buffers to an editor other than SimpleTextEditor.
458: /*
459: /* Revision 1.13 2000/12/04 23:51:16 smulloni
460: /* added ImageViewer; fixed word in SimpleTextEditor
461: /*
462: /* Revision 1.12 2000/12/03 23:53:26 smulloni
463: /* added license and copyright preamble to java files.
464: /*
465: /* Revision 1.11 2000/12/03 20:40:06 smulloni
466: /* reconciling the minos.skunk.org cvs repository with sourceforge.
467: /*
468: /* Revision 1.11 2000/12/01 22:54:14 smullyan
469: /* added undo and basic editing functionality to text editor; defined some
470: /* keybindings in an inflexible way that will have to be changed soon.
471: /*
472: /* Revision 1.10 2000/12/01 16:25:54 smullyan
473: /* improvements to look and feel; fixed NPE in DAVFile; new actions for text
474: /* editor
475: /*
476: /* Revision 1.9 2000/11/29 23:16:06 smullyan
477: /* adding first rough cut of search capability to the text editor. View
478: /* is being updated to allow components to be docked into the status bar.
479: /*
480: /* Revision 1.8 2000/11/28 19:36:20 smullyan
481: /* some bug fixes; notes.
482: /*
483: /* Revision 1.7 2000/11/28 00:17:31 smullyan
484: /* a fix that is meant to prevent a null pointer exception I noticed in the logs.
485: /*
486: /* Revision 1.6 2000/11/28 00:01:41 smullyan
487: /* added a status bar/minibuffer, with a location field showing the current line and
488: /* column number (for the SimpleTextEditor and kin only).
489: /*
490: /* Revision 1.5 2000/11/20 23:30:25 smullyan
491: /* more editor integration work.
492: /*
493: /* Revision 1.4 2000/11/18 04:36:07 smullyan
494: /* work on StateMonitor and related functionality.
495: /*
496: /* Revision 1.3 2000/11/16 23:16:48 smullyan
497: /* more preliminary work on editor integration
498: /*
499: /* Revision 1.2 2000/11/15 20:17:05 smullyan
500: /* added a Buffer interface, which is a wrapper around a displayable component.
501: /*
502: /* Revision 1.1 2000/11/15 19:45:37 smullyan
503: /* beginning of revamp of application to include multiple buffers.
504: /* */
|