1 package com.explosion.expfmodules.texteditor;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.awt.BorderLayout;
23 import java.awt.Color;
24 import java.awt.Component;
25 import java.awt.Font;
26 import java.awt.event.ActionEvent;
27 import java.awt.event.ActionListener;
28 import java.awt.event.FocusEvent;
29 import java.awt.event.FocusListener;
30 import java.io.File;
31 import java.io.FileReader;
32 import java.io.FileWriter;
33 import java.io.IOException;
34 import java.util.HashMap;
35 import java.util.Map;
36
37 import javax.swing.JFileChooser;
38 import javax.swing.JInternalFrame;
39 import javax.swing.JOptionPane;
40 import javax.swing.JPanel;
41 import javax.swing.JScrollPane;
42 import javax.swing.event.DocumentEvent;
43 import javax.swing.event.DocumentListener;
44 import javax.swing.text.JTextComponent;
45
46 import com.explosion.expf.Application;
47 import com.explosion.expf.Closeable;
48 import com.explosion.expf.ExpActionListener;
49 import com.explosion.expf.ExpComponent;
50 import com.explosion.expf.ExpConstants;
51 import com.explosion.expf.ExpFrame;
52 import com.explosion.utilities.exception.ExceptionManagerFactory;
53
54
55 /***
56 * @author Stephen Cowx
57 * Date created:@06-Feb-2003
58 * A tool for editing documents. It can be added as child of a parent component or exist on it's own in its
59 * own frame
60 */
61
62 public class TextEditor extends JPanel implements DocumentListener, ExpComponent, Closeable
63 {
64 private static final int TWO_MB = 1024*1024*2;
65
66 long dateLastEdited = 0;
67 long originalSize = 0;
68
69
70 private boolean dirty = false;
71 private TextDocument document;
72
73
74 private JScrollPane scrollPane = new JScrollPane();
75 private LineNumberTextEditor textComponent;
76
77
78 private TextEditorLocalListener localListener = null;
79 private TextEditorGlobalListener globalListener = null;
80
81 private boolean modificationDecisionAlreadyMade = false;
82 private Component parentLocalComponent;
83
84 /***
85 * Initialises a new empty text editor
86 */
87 public TextEditor(int documentNumber) throws Exception
88 {
89 String extension = (String) TextEditorModuleManager.instance().getPreference(TextEditorConstants.DEFAULTEXTENSION).getValue();
90 document = new TextDocument(documentNumber, extension);
91 this.setName("TEXT_EDITOR");
92 this.parentLocalComponent = this;
93 init();
94 }
95
96 /***
97 * Initialises a text editor from a file
98 */
99 public TextEditor(File file) throws Exception
100 {
101 this(file, null);
102 }
103
104 /***
105 * Initialises a text editor from a file
106 */
107 public TextEditor(File file, Component parentLocalComponent) throws Exception
108 {
109 if (parentLocalComponent != null)
110 this.parentLocalComponent = parentLocalComponent;
111 else
112 this.parentLocalComponent = this;
113
114 document = new TextDocument(file);
115 document.setDocumentName(file.getName());
116 this.setName("TEXT_EDITOR");
117
118 init();
119 }
120
121 /***
122 * This method initialises the gui
123 */
124 private void init() throws Exception
125 {
126
127 Application.addToGlobalCookie(ExpConstants.MENU_CLOSEALL,1);
128 Application.addToLocalCookie(ExpConstants.MENU_EDIT,1,this);
129 Application.addToLocalCookie(ExpConstants.MENU_CLOSE,1,this);
130 Application.addToLocalCookie(ExpConstants.MENU_SAVEAS,1,this);
131 Application.addToLocalCookie(ExpConstants.MENU_SELECTALL,1,this);
132
133
134
135 localListener = new TextEditorLocalListener(this, parentLocalComponent);
136 textComponent = new LineNumberTextEditor(parentLocalComponent,"1",false);
137
138 if (parentLocalComponent == this)
139 {
140 globalListener = new TextEditorGlobalListener(this);
141 }
142
143
144 this.setLayout(new BorderLayout());
145 this.setDoubleBuffered(true);
146 this.add(textComponent, BorderLayout.CENTER);
147
148 scrollPane.setDoubleBuffered(true);
149 this.setDoubleBuffered(true);
150
151 load();
152
153 FileChangeListener.monitor(this);
154
155 this.applyPreferences();
156
157 textComponent.getEditorTextComponent().getDocument().addDocumentListener(this);
158 }
159
160 /***
161 *
162 * Loads the file that this TextEDitor is pointing to. If it isn't pointing tio a file then it does nothing.
163 * @throws IOException
164 */
165 public void load() throws IOException
166 {
167 if (document.getFile() != null)
168 {
169 if (document.getFile().length() > TWO_MB)
170 {
171 textComponent.getEditorTextComponent().read(new FileReader(document.getFile()), "A File");
172 }
173 else
174 {
175 textComponent.getEditorTextComponent().read(new FileReader(document.getFile()), "A File");
176 }
177 }
178 clean();
179 }
180
181 /***
182 * Returns the string command which will make this editor close itself
183 * @see com.explosion.expf.Closeable#getCloseCommand()
184 * @return
185 */
186 public String getCloseCommand()
187 {
188 return ExpConstants.MENU_CLOSE;
189 }
190
191 /***
192 * This method closes the editor
193 */
194 public boolean close() throws Exception
195 {
196 boolean close = true;
197 int decision = -1;
198 if (dirty)
199 {
200 decision = JOptionPane.showConfirmDialog(this,"'" + document.getDocumentName() + "' has changed, would you like to save it ?","Save",JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE);
201 if (decision == JOptionPane.YES_OPTION)
202 {
203 close = save();
204 }
205 else if (decision == JOptionPane.CANCEL_OPTION)
206 {
207 close = false;
208 }
209 else
210 {
211 close = true;
212 }
213 }
214
215 if (close)
216 {
217 Application.removeFromGlobalCookie(ExpConstants.MENU_CLOSEALL,1);
218 Application.removeFromGlobalCookie(ExpConstants.MENU_SAVEALL,1);
219 }
220
221 return close;
222 }
223 /***
224 * save the current document
225 */
226 public boolean save() throws Exception
227 {
228 try
229 {
230 if (document.getDocumentNumber() == -1)
231 {
232 textComponent.getEditorTextComponent().write(new FileWriter(getFile()));
233 clean();
234 return true;
235 }
236 else
237 {
238 return saveAs();
239 }
240 }
241 catch (Exception ex)
242 {
243 ExceptionManagerFactory.getExceptionManager().manageException(ex, "Exception caught while saving document.");
244 }
245 return true;
246 }
247 /***
248 * Save the document as something that it is not at the moment
249 */
250 public boolean saveAs() throws Exception
251 {
252 try
253 {
254 boolean done = false;
255 while (!done)
256 {
257
258 JFileChooser fc = new JFileChooser();
259 File currentFile = getFile();
260 if (currentFile == null)
261 currentFile = new File(System.getProperty("user.dir"));
262 fc.setCurrentDirectory(currentFile);
263 fc.setName("Save as");
264
265 int result = fc.showSaveDialog(this);
266 if (result == JFileChooser.APPROVE_OPTION)
267 {
268
269 File selectedfile = fc.getSelectedFile();
270 int overWriteDecision = -1;
271
272 if (selectedfile.exists())
273 overWriteDecision =
274 JOptionPane.showConfirmDialog(
275 this,
276 "'" + selectedfile.getName() + "' already exists. Overwrite ?",
277 "File exists !",
278 JOptionPane.YES_NO_CANCEL_OPTION,
279 JOptionPane.INFORMATION_MESSAGE);
280
281 if (overWriteDecision == JOptionPane.YES_OPTION
282 || overWriteDecision == -1)
283 {
284 textComponent.getEditorTextComponent().write(new FileWriter(selectedfile));
285 document.setDocumentName(selectedfile.getName());
286 document.setDocumentNumber(-1);
287 document.setFile(selectedfile);
288 clean();
289
290 return true;
291 }
292 }
293 else
294 {
295
296 return false;
297 }
298 }
299 }
300 catch (Exception ex)
301 {
302 ExceptionManagerFactory.getExceptionManager().manageException(ex, "Exception caught while saving document.");
303 }
304 return true;
305 }
306
307 public void applyPreferences()
308 {
309
310
311 textComponent.getEditorTextComponent().setForeground((Color) TextEditorModuleManager.instance().getPreference(TextEditorConstants.COLORS_FOREGROUND).getValue());
312 textComponent.getEditorTextComponent().setBackground((Color) TextEditorModuleManager.instance().getPreference(TextEditorConstants.COLORS_BACKGROUND).getValue());
313 textComponent.getEditorTextComponent().setSelectedTextColor((Color) TextEditorModuleManager.instance().getPreference(TextEditorConstants.COLOR_SELECTEDFORGROUND).getValue());
314 textComponent.getEditorTextComponent().setSelectionColor((Color) TextEditorModuleManager.instance().getPreference(TextEditorConstants.COLOR_SELECTEDBACKGROUND).getValue());
315 textComponent.getEditorTextComponent().setFont((Font) TextEditorModuleManager.instance().getPreference(TextEditorConstants.FONT).getValue());
316 textComponent.getEditorTextComponent().setCaretColor((Color) TextEditorModuleManager.instance().getPreference(TextEditorConstants.CARET_COLOR).getValue());
317
318 textComponent.getMarginTextArea().setForeground((Color) TextEditorModuleManager.instance().getPreference(TextEditorConstants.COLORS_FOREGROUND).getValue());
319 textComponent.getMarginTextArea().setBackground((Color) TextEditorModuleManager.instance().getPreference(TextEditorConstants.COLORS_BACKGROUND).getValue());
320 textComponent.getMarginTextArea().setSelectedTextColor((Color) TextEditorModuleManager.instance().getPreference(TextEditorConstants.COLOR_SELECTEDFORGROUND).getValue());
321 textComponent.getMarginTextArea().setSelectionColor((Color) TextEditorModuleManager.instance().getPreference(TextEditorConstants.COLOR_SELECTEDBACKGROUND).getValue());
322 textComponent.getMarginTextArea().setFont((Font) TextEditorModuleManager.instance().getPreference(TextEditorConstants.FONT).getValue());
323 textComponent.getMarginTextArea().setCaretColor((Color) TextEditorModuleManager.instance().getPreference(TextEditorConstants.CARET_COLOR).getValue());
324
325 textComponent.getEditorTextComponent().applyPreferences();
326 }
327
328 public ActionListener getGlobalListener()
329 {
330 return null;
331 }
332
333 /***
334 * Returns a boolean value indicating whether this document has changed since it was last saved
335 */
336 public boolean isDirty() throws Exception
337 {
338 return dirty;
339 }
340
341 public TextDocument getDocument()
342 {
343 return document;
344 }
345
346 /***
347 * Code to support document Listener
348 */
349 public void changedUpdate(DocumentEvent e)
350 {
351 if (!dirty)
352 dirty();
353 }
354 /***
355 * Code to support document Listener
356 */
357 public void insertUpdate(DocumentEvent e)
358 {
359 if (!dirty)
360 dirty();
361 }
362
363 /***
364 * Code to support document Listener
365 */
366 public void removeUpdate(DocumentEvent e)
367 {
368 if (!dirty)
369 dirty();
370 }
371
372 private void dirty()
373 {
374 dirty = true;
375 try
376 {
377
378 JInternalFrame frame = ((ExpFrame)Application.getApplicationFrame()).getFrameWithComponent(this,ExpFrame.DOC_FRAME_LAYER.intValue());
379 frame.setTitle(document.getDocumentName() + "*");
380 }
381 catch (Exception e)
382 {}
383
384 Application.addToGlobalCookie(ExpConstants.MENU_SAVEALL,1);
385 Application.addToLocalCookie(ExpConstants.MENU_SAVE,1,parentLocalComponent);
386 }
387
388 private void clean()
389 {
390 dirty = false;
391 try
392 {
393
394 JInternalFrame frame = ((ExpFrame)Application.getApplicationFrame()).getFrameWithComponent(this,ExpFrame.DOC_FRAME_LAYER.intValue());
395 frame.setTitle(document.getDocumentName());
396 }
397 catch (Exception e)
398 {}
399 Application.removeFromGlobalCookie(ExpConstants.MENU_SAVEALL,1);
400 Application.removeFromLocalCookie(ExpConstants.MENU_SAVE,1,parentLocalComponent);
401 modificationDecisionAlreadyMade = false;
402 resetModifications();
403 }
404
405 public File getFile()
406 {
407 return document.getFile();
408 }
409
410 public JTextComponent getTextComponent()
411 {
412 return textComponent.getEditorTextComponent();
413 }
414
415 /***
416 * This method sets the date modified and filesize
417 * of the file as it is on disk. This happends on save and load,
418 * whenever a clean() is called.
419 */
420 private void resetModifications()
421 {
422 File file = document.getFile();
423 if (file != null)
424 {
425 if (file.exists())
426 {
427 this.dateLastEdited = file.lastModified();
428 this.originalSize = file.length();
429 }
430 }
431 }
432
433 /***
434 * This method checks to see if the file size is the same as it was the last time
435 * the filesize was checked. If it isn't the user is asked if they would like
436 * to have the file reloaded.
437 * Specifyng the force flag as true will skip the user confirmation and just reload the file.
438 *
439 * @param force
440 */
441 public void checkModifications(boolean force)
442 {
443
444 try
445 {
446 File newFile = document.getFile();
447 if (newFile != null)
448 {
449 if (newFile.lastModified() != dateLastEdited || newFile.length() != originalSize)
450 {
451 if (force)
452 {
453 load();
454 }
455 else
456 {
457 if (!modificationDecisionAlreadyMade)
458 {
459 int decision = JOptionPane.showConfirmDialog(Application.getApplicationFrame(),"'" + document.getDocumentName() + "' has changed, would you like to reload it ?","Reload?",JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE);
460 if (decision == JOptionPane.YES_OPTION)
461 {
462 load();
463 }
464
465 modificationDecisionAlreadyMade = true;
466 }
467 }
468 }
469 }
470 }
471 catch (Exception e)
472 {
473 ExceptionManagerFactory.getExceptionManager().manageException(e,"Exception caught while reloading file");
474 }
475 }
476
477 }
478
479 class TextEditorLocalListener implements ExpActionListener
480 {
481
482 private Map map;
483 private int newDocumentNumbers = 1;
484 private TextEditor editor;
485
486 /***
487 * Constructor for TextEditorLocalListener.
488 */
489 public TextEditorLocalListener(TextEditor editor, Component parentReference)
490 {
491 this.editor = editor;
492 map = new HashMap();
493 map.put(ExpConstants.MENU_SAVE,ExpConstants.MENU_SAVE);
494 map.put(ExpConstants.MENU_SAVEAS,ExpConstants.MENU_SAVEAS);
495 map.put(ExpConstants.MENU_CLOSE,ExpConstants.MENU_CLOSE);
496 map.put(ExpConstants.MENU_REFRESH,ExpConstants.MENU_REFRESH);
497
498
499 ((ExpFrame) Application.getApplicationFrame()).getListener().addLocalActionListener(this,parentReference);
500 }
501
502 /***
503 * @see package com.explosion.expf.Interfaces.ExpActionListener#getListensFor()
504 */
505 public Map getListensFor()
506 {
507 return map;
508 }
509
510 /***
511 * @see java.awt.event.ActionListener#actionPerformed(ActionEvent)
512 */
513 public void actionPerformed(ActionEvent e)
514 {
515 try
516 {
517 if (e.getActionCommand().equals(ExpConstants.MENU_SAVE)) { editor.save(); }
518 else if (e.getActionCommand().equals(ExpConstants.MENU_SAVEAS)) { editor.saveAs(); }
519 else if (e.getActionCommand().equals(ExpConstants.MENU_CLOSE))
520 {
521 if (editor.close())
522 ((ExpFrame) Application.getApplicationFrame()).closeFrameWithComponent(editor, ExpFrame.DOC_FRAME_LAYER);
523 }
524 else if (e.getActionCommand().equals(ExpConstants.MENU_REFRESH))
525 {
526 editor.load();
527 }
528 }
529 catch (Exception ex)
530 {
531 com.explosion.utilities.exception.ExceptionManagerFactory.getExceptionManager().manageException(ex, "Exception caught while responding to SimpleProcess Event." );
532 }
533
534 }
535
536 }
537
538 class TextEditorGlobalListener implements ExpActionListener
539 {
540
541 private HashMap map;
542 private int newDocumentNumbers = 1;
543 private TextEditor editor;
544
545 /***
546 * Constructor for TextEditorLocalListener.
547 */
548 public TextEditorGlobalListener(TextEditor editor)
549 {
550 this.editor = editor;
551 map = new HashMap();
552 map.put(ExpConstants.MENU_CLOSEALL,ExpConstants.MENU_CLOSEALL);
553 map.put(ExpConstants.MENU_SAVEALL,ExpConstants.MENU_SAVEALL);
554
555
556 ((ExpFrame) Application.getApplicationFrame()).getListener().addGlobalActionListener(this, editor);
557 }
558
559 /***
560 * @see package com.explosion.expf.Interfaces.ExpActionListener#getListensFor()
561 */
562 public Map getListensFor()
563 {
564 return map;
565 }
566
567 /***
568 * @see java.awt.event.ActionListener#actionPerformed(ActionEvent)
569 */
570 public void actionPerformed(ActionEvent e)
571 {
572 try
573 {
574 if (e.getActionCommand().equals(ExpConstants.MENU_SAVEALL)) { editor.save(); }
575 else if (e.getActionCommand().equals(ExpConstants.MENU_CLOSEALL))
576 {
577 if (editor.close())
578 ((ExpFrame) Application.getApplicationFrame()).closeFrameWithComponent(editor, ExpFrame.DOC_FRAME_LAYER);
579 }
580 }
581 catch (Exception ex)
582 {
583 com.explosion.utilities.exception.ExceptionManagerFactory.getExceptionManager().manageException(ex, "Exception caught while responding to SimpleProcess Event." );
584 }
585
586 }
587
588 }
589
590 /***
591 * This listener checks to see if the file has changed every time the text component gains focus
592 * @author Stephen
593 * Created on Apr 23, 2004
594 */
595 class FileChangeListener implements FocusListener
596 {
597 File file;
598 TextEditor textEditor;
599
600 /***
601 * Constructor
602 * @param textEditor
603 * @param file
604 */
605 private FileChangeListener(TextEditor textEditor)
606 {
607 this.file = textEditor.getDocument().getFile();
608 this.textEditor = textEditor;
609 textEditor.getTextComponent().addFocusListener(this);
610 }
611
612 /***
613 * Register this editor as one whose file which requires listening to
614 * @param editor
615 */
616 public static void monitor(TextEditor editor)
617 {
618 if (editor.getDocument().getFile() != null)
619 new FileChangeListener(editor);
620 }
621
622 /***
623 * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent)
624 * @param arg0
625 */
626 public void focusGained(FocusEvent arg0)
627 {
628 textEditor.checkModifications(false);
629 }
630
631 /***
632 * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent)
633 * @param arg0
634 */
635 public void focusLost(FocusEvent arg0)
636 {}
637 }