View Javadoc

1   package com.explosion.expfmodules.texteditor;
2   
3   /* =============================================================================
4    *       
5    *     Copyright 2004 Stephen Cowx
6    *
7    *     Licensed under the Apache License, Version 2.0 (the "License");
8    *     you may not use this file except in compliance with the License.
9    *     You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   *     Unless required by applicable law or agreed to in writing, software
14   *     distributed under the License is distributed on an "AS IS" BASIS,
15   *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   *     See the License for the specific language governing permissions and
17   *     limitations under the License.
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    /* General variables */
70    private boolean dirty = false;
71    private TextDocument document;
72    
73    /* Gui components  */
74    private JScrollPane scrollPane = new JScrollPane();
75    private LineNumberTextEditor textComponent;
76    
77    /* Local listener */
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     /* Initialise cookies */
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     /* Initialise the listeners, if it has a global parent already then don't initialise a global listener for it */
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     /* Initilaise the gui components */
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         /* Pop up a file chooser */
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         /* Let the user select a file */
265         int result = fc.showSaveDialog(this);
266         if (result == JFileChooser.APPROVE_OPTION)
267         {
268           /* If the have aproved a file */
269           File selectedfile = fc.getSelectedFile();
270           int overWriteDecision = -1;
271           /* Check if it exists */
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           /* If it exists and they want to overwrite || it doesn't exist then save and return FileSaved*/
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           /* If they decide to cancel (ie not save), return file not saved */
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 	//textComponent.getEditorTextComponent().setWrapStyleWord(true);
310 	//textComponent.getEditorTextComponent().setLineWrap(((Boolean) TextEditorModuleManager.instance().getPreference(TextEditorConstants.WORDWRAP).getValue()).booleanValue());
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 						/* This flag is to stop it asking you in an infinite loop */
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)//, ExpActionListener textComponentListener)
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     /* Register this listener with the CompoundListener so that it listens for local events */
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     /* Register this listener with the CompoundListener so that it listens for local events */
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 }