Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/memoire/bu/BuTextArea.java


1   /**
2    * @modification $Date: 2002/11/14 19:25:32 $
3    * @statut       unstable
4    * @file         BuTextArea.java
5    * @version      0.36
6    * @author       Romain Guy
7    * @email        guy.romain@bigfoot.com
8    * @license      GNU General Public License 2 (GPL2)
9    * @copyright    1998-2001 Guillaume Desnoix
10   */
11  package com.memoire.bu;
12  import com.memoire.bu.*;
13  import com.memoire.dnd.*;
14  import com.memoire.fu.*;
15  import com.memoire.re.*;
16  
17  
18  /*
19   * 19:13:20 13/06/99
20   *
21   * JextTextAreaLite.java - A 'lite' extended JTextArea
22   * Copyright (C) 1998-1999 Romain Guy
23   * Especially designed for Nicolas Warneck
24   * powerteam@chez.com
25   * www.chez.com/powerteam
26   *
27   * This program is free software; you can redistribute it and/or
28   * modify it under the terms of the GNU General Public License
29   * as published by the Free Software Foundation; either version 2
30   * of the License, or any later version.
31   *
32   * This program is distributed in the hope that it will be useful,
33   * but WITHOUT ANY WARRANTY; without even the implied warranty of
34   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
35   * GNU General Public License for more details.
36   *
37   * You should have received a copy of the GNU General Public License
38   * along with this program; if not, write to the Free Software
39   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
40   *
41   *
42   * A few changes and adaptations 25/5/2000 (C) Guillaume Desnoix
43   */
44  
45  import java.io.*;
46  import java.awt.*;
47  // import gnu.regexp.*;
48  import java.util.zip.*;
49  import java.awt.event.*;
50  
51  import javax.swing.*;
52  import javax.swing.undo.*;
53  import javax.swing.event.*;
54  import javax.swing.text.*;
55  
56  /**
57   * This component extends a JTextArea, providing methods to
58   * undo/redo, find/replace and read/save.
59   *
60   * This component extends a JTextArea. This component provides
61   * its own methods to read and save files (even to zip them).
62   * @author  Romain Guy
63   * @email   guy.romain@bigfoot.com
64   * @version 1.1
65   */
66  public class BuTextArea extends JTextArea
67         implements UndoableEditListener, DocumentListener,
68                    BuTextComponentInterface
69  {
70    /** This constant defines an open dialog box. */
71    public static final int OPEN = 0;
72    /** This constant defines a save dialog box. */
73    public static final int SAVE = 1;
74  
75    // Private declaration
76  
77    private RE regexp;
78    private CompoundEdit compoundEdit;
79    private String fontName, currentFile;
80    private int anchor, fontSize, fontStyle;
81    private boolean dirty, newText, operation;
82    private UndoManager undo = new UndoManager();
83  
84    /** This constant defines the size of the buffer used to read files */
85    private static final int BUFFER_SIZE = 32768;
86  
87    /**
88     * The constructor add the necessary listeners, set some stuffs
89     * (caret color, borers, fonts...).
90     */
91  
92    public BuTextArea()
93    {
94      this(15,40);
95    }
96  
97    public BuTextArea(int _lines, int _cols)
98    {
99      super(_lines, _cols);
100 
101     // setBorder(null);
102     // setCaretColor(Color.red);
103 
104     getDocument().addDocumentListener(this);
105     getDocument().addUndoableEditListener(this);
106 
107     Font defaultFont = getFont(); // new Font("Monospaced", Font.PLAIN, 12);
108     fontName = defaultFont.getName();
109     fontSize = defaultFont.getSize();
110     fontStyle = defaultFont.getStyle();
111     // setFont(defaultFont);
112   }
113 
114   // Anti-aliasing
115 
116   public void paint(Graphics _g)
117   {
118     BuLib.setAntialiasing(_g);
119     super.paint(_g);
120   }
121 
122   public void select()
123   {
124     select(0,getLength());
125   }
126 
127   public void duplicate()
128   {
129     int s=getSelectionStart();
130     int e=getSelectionEnd();
131     if(s<e)
132     {
133       copy();
134       setCaretPosition(e);
135       paste();
136       setSelectionEnd(e+(e-s));
137       setSelectionStart(e);
138     }
139   }
140 
141   public void go(int _line)
142   {
143     Element map =getDocument().getDefaultRootElement();
144     Element line=map.getElement(_line);
145     select(line.getStartOffset(),line.getEndOffset());
146   }
147 
148   /**
149    * Display a file chooser dialog box.
150    * @param owner <code>Component</code> which 'owns' the dialog
151    * @param mode Can be either <code>LOAD</code> or <code>SAVE</code>
152    * @return The path to selected file, null otherwise
153    */
154 
155   public static String chooseFile(Component owner, int mode)
156   {
157     BuFileChooser chooser = new BuFileChooser();
158     if (mode == OPEN)
159       chooser.setDialogType(BuFileChooser.OPEN_DIALOG);
160     else if (mode == SAVE)
161       chooser.setDialogType(BuFileChooser.SAVE_DIALOG);
162     chooser.setFileSelectionMode(BuFileChooser.FILES_ONLY);
163     chooser.setFileHidingEnabled(true);
164     if (chooser.showDialog(owner, null) == BuFileChooser.APPROVE_OPTION)
165       return chooser.getSelectedFile().getAbsolutePath();
166     return null;
167   }
168 
169   /**
170    * Display a sample message in a dialog box.
171    * @param message The message to display
172    */
173 
174   public static void showMessage(String message)
175   {
176     JOptionPane.showMessageDialog(null, message, "Message", JOptionPane.INFORMATION_MESSAGE);
177   }
178 
179   /**
180    * Display an error message in a dialog box.
181    * @param message The message to display
182    */
183 
184   public static void showError(String message)
185   {
186     JOptionPane.showMessageDialog(null,message,"Error",
187           JOptionPane.ERROR_MESSAGE);
188   }
189   /**
190    * Return current font's name
191    */
192 
193   public String getFontName()
194   {
195     return fontName;
196   }
197 
198   /**
199    * Return current font's size
200    */
201 
202   public int getFontSize()
203   {
204     return fontSize;
205   }
206 
207   /**
208    * Return current font's style (bold, italic...)
209    */
210 
211   public int getFontStyle()
212   {
213     return fontStyle;
214   }
215 
216   /**
217    * Set the font which has to be used.
218    * @param name The name of the font
219    */
220 
221   public void setFontName(String name)
222   {
223     fontName = name;
224     changeFont();
225   }
226 
227   /**
228    * Set the size of the font.
229    * @param size The new font's size
230    */
231 
232   public void setFontSize(int size)
233   {
234     fontSize = size;
235     changeFont();
236     repaint();
237   }
238 
239   /**
240    * Set the style of the font.
241    * @param style The new style to apply
242    */
243 
244   public void setFontStyle(int style)
245   {
246     fontStyle = style;
247     changeFont();
248     repaint();
249   }
250 
251   /**
252    * Set the new font.
253    */
254 
255   private void changeFont()
256   {
257     setFont(new Font(fontName, fontStyle, fontSize));
258   }
259 
260   /**
261    * Return the full path of the opened file.
262    */
263 
264   public String getCurrentFile()
265   {
266     return currentFile;
267   }
268 
269   /**
270    * When an operation has began, setChanged() cannot be called.
271    * This is very important when we need to insert or remove some
272    * parts of the texte without turning on the 'to_be_saved' flag.
273    */
274 
275   public void beginOperation()
276   {
277     operation = true;
278   }
279 
280   /**
281    * Calling this will allow the DocumentListener to use setChanged().
282    */
283 
284   public void endOperation()
285   {
286     operation = false;
287   }
288 
289   /**
290    * Return true if we can use the setChanged() method,
291    * false otherwise.
292    */
293 
294   public boolean getOperation()
295   {
296     return operation;
297   }
298 
299   /**
300    * Set a new file. We first ask the user if he'd like to save its
301    * changes (if some have been made).
302    */
303 
304   public void newFile()
305   {
306     beginOperation();                            // we don't want to see a 'modified' message
307     if (isDirty() && !isEmpty())
308     {
309       int response = JOptionPane.showConfirmDialog(null, "Do you want to save your changes ?", "Save", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
310       switch (response)
311       {
312         case 0:
313           saveContent();
314           break;
315         case 1:
316           break;
317         case 2:
318           endOperation();
319           return;
320         default:
321           endOperation();
322           return;
323       }
324     }
325     // we have to create a new document, so we remove
326     // old listeners and create new ones below
327     getDocument().removeUndoableEditListener(this);
328     getDocument().removeDocumentListener(this);
329     clean();
330     newText = true;
331     currentFile = null;
332     setEditable(true);
333     setText("");
334     setAnchor(0);
335     discard();
336     getDocument().addUndoableEditListener(this);
337     getDocument().addDocumentListener(this);
338     endOperation();
339   }
340 
341   /**
342    * This is just to reduce code size of other classes.
343    * @param off The line index
344    * @return The offset in the text where the line begins
345    */
346 
347   public int getLineStartOffset(int off)
348   {
349     return getDocument().getDefaultRootElement().getElement(off).getStartOffset();
350   }
351 
352   /**
353    * Called to save current content in specified zip file.
354    * Call zip(String file) but asks user for overwriting if
355    * file already exists.
356    */
357 
358   public void zipContent()
359   {
360     if (getText().equals("")) return;
361     String zipFile = chooseFile(this, SAVE);
362     if (zipFile != null)
363     {
364       if (!zipFile.endsWith(".zip")) zipFile += ".zip";
365       if (!(new File(zipFile)).exists())
366         zip(zipFile);
367       else
368       {
369         int response = JOptionPane.showConfirmDialog(null, "File already exists, overwrite it ?", "Save", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
370         switch (response)
371         {
372           case 0:
373             zip(zipFile);
374             break;
375           case 1:
376             break;
377           default:
378             return;
379         }
380       }
381     }
382   }
383 
384   /**
385    * Zip text area content into specified file.
386    * @param zipFile The file name where to zip the text
387    */
388 
389   public void zip(String zipFile)
390   {
391     try
392     {
393       ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile));
394       out.putNextEntry(new ZipEntry((new File(currentFile)).getName()));
395       // we ensure we use system's carriage return char
396       String newline = System.getProperty("line.separator");
397       Element map = getDocument().getDefaultRootElement();
398       // we zip the text line by line
399       for (int i = 0; i < map.getElementCount(); i++)
400       {
401         Element line = map.getElement(i);
402         int start = line.getStartOffset();
403         byte[] buf = (getText(start, line.getEndOffset() - start - 1) + newline).getBytes();
404         out.write(buf, 0, buf.length);
405       }
406       out.closeEntry();
407       out.close();
408     } catch(IOException ioe) {
409       showError("Error has occured while ziping");
410     } catch(BadLocationException ble) {
411       showError("Error has occured while ziping");
412     }
413   }
414 
415   /**
416    * Called to save this component's content.
417    * Call save(String file) but let the user choosing a file name.
418    * In the case the user choosed an existing file, we ask him if
419    * he really wants to overwrite it.
420    */
421 
422   public void saveContent()
423   {
424     String fileToSave = chooseFile(this, SAVE);
425     if (fileToSave != null)
426     {
427       if (!(new File(fileToSave)).exists())
428         save(fileToSave);
429       else
430       {
431         int response = JOptionPane.showConfirmDialog(null, "File already exists, overwrite it ?", "Save", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
432         switch (response)
433         {
434           case 0:
435             save(fileToSave);
436             break;
437           case 1:
438             break;
439           default:
440             return;
441         }
442       }
443     }
444   }
445 
446   /**
447    * Store the text in a specified file.
448    * @param file The file in wich we'll write the text
449    */
450 
451   public void save(String file)
452   {
453     try
454     {
455       OutputStream outs = new FileOutputStream(new File(file));
456       BufferedWriter out = new BufferedWriter(new OutputStreamWriter(outs), BUFFER_SIZE);
457       // we ensure we use system's carriage return char
458       String newline = System.getProperty("line.separator");
459       Element map = getDocument().getDefaultRootElement();
460       // we write the file line by line
461       for (int i = 0; i < map.getElementCount(); i++)
462       {
463         Element line = map.getElement(i);
464         int start = line.getStartOffset();
465         out.write(getText(start, line.getEndOffset() - start - 1));
466         out.write(newline);
467       }
468       out.close();
469       outs.close();
470       if (!file.equals(currentFile)) currentFile = file;
471     } catch (IOException ioe) {
472       showError("Error has occured while saving");
473     } catch (BadLocationException ble) {
474       showError("Error has occured while saving");
475     }
476   }
477 
478   /**
479    * Called to load a new file in the text area.
480    * Determines which line separator (\n, \r\n...) are used in the
481    * file to open. Convert'em into Swing line separator (\n).
482    * @param path The path of the file to be loaded
483    */
484 
485   public void open(String path)
486   {
487     beginOperation();
488     // we do the same thing as in newFile() for the listeners
489     getDocument().removeUndoableEditListener(this);
490     getDocument().removeDocumentListener(this);
491     clean();
492     discard();
493     InputStream is;
494     StringBuffer buffer = new StringBuffer();
495     try
496     {
497       File toLoad = new File(path);
498       // we check if the file is read only or not
499       if (!toLoad.canWrite())
500         setEditable(false);
501       else if (!isEditable())
502         setEditable(true);
503       is = new FileInputStream(toLoad);
504       InputStreamReader in = new InputStreamReader(is);
505       char[] buf = new char[BUFFER_SIZE];
506       int len;
507       int lineCount = 0;
508       boolean CRLF = false;
509       boolean CROnly = false;
510       boolean lastWasCR = false;
511 
512       // we read the file until the end of it (amazing, hu ?)
513       while ((len = in.read(buf, 0, buf.length)) != -1)
514       {
515         int lastLine = 0;
516         for (int i = 0; i < len; i++)
517         {
518           switch(buf[i])
519           {
520             // and we convert system's carriage return char into \n
521             case '\r':
522               if (lastWasCR)
523               {
524                 CROnly = true;
525                 CRLF = false;
526               } else
527                 lastWasCR = true;
528               buffer.append(buf, lastLine, i - lastLine);
529               buffer.append('\n');
530               lastLine = i + 1;
531               break;
532             case '\n':
533               if (lastWasCR)
534               {
535                 CROnly = false;
536                 CRLF = true;
537                 lastWasCR = false;
538                 lastLine = i + 1;
539               } else {
540                 CROnly = false;
541                 CRLF = false;
542                 buffer.append(buf, lastLine, i - lastLine);
543                 buffer.append('\n');
544                 lastLine = i + 1;
545               }
546               break;
547             default:
548               if (lastWasCR)
549               {
550                 CROnly = true;
551                 CRLF = false;
552                 lastWasCR = false;
553               }
554               break;
555           }
556         }
557         buffer.append(buf, lastLine, len - lastLine);
558       }
559       in.close();
560       if (buffer.length() != 0 && buffer.charAt(buffer.length() - 1) == '\n')
561         buffer.setLength(buffer.length() - 1);
562 
563       // we clear the area
564       getDocument().remove(0, getLength());
565       // we put the text in it
566       getDocument().insertString(0, buffer.toString(), null);
567       currentFile = path;
568       setCaretPosition(0);
569       newText = false;
570       getDocument().addUndoableEditListener(this);
571       getDocument().addDocumentListener(this);
572     } catch(BadLocationException bl) {
573       bl.printStackTrace();
574     } catch(FileNotFoundException fnf) {
575       showError(path + " not found !");
576     } catch(IOException io) {
577       showError(io.toString());
578     }
579     endOperation();
580   }
581 
582   /**
583    * Return true if current text is new, false otherwise.
584    */
585 
586   public boolean isNew()
587   {
588     return newText;
589   }
590 
591   /**
592    * Return true if area is empty, false otherwise.
593    */
594 
595   public boolean isEmpty()
596   {
597     if (getLength() == 0)
598       return true;
599     else
600       return false;
601   }
602 
603 
604   /**
605    * Return true if area content has changed, false otherwise.
606    */
607 
608   public boolean isDirty()
609   {
610     return dirty;
611   }
612 
613   /**
614    * Called when the content of the area has changed.
615    */
616 
617   public void setDirty()
618   {
619     dirty = true;
620   }
621 
622   /**
623    * Called after having saved or created a new document to ensure
624    * the content isn't 'dirty'.
625    */
626 
627   public void clean()
628   {
629     dirty = false;
630   }
631 
632   /**
633    * Discard all edits contained in the UndoManager.
634    */
635 
636   public void discard()
637   {
638     undo.discardAllEdits();
639   }
640 
641   /**
642    * Useful for the GUI.
643    */
644   public UndoManager getUndo()
645   {
646     return undo;
647   }
648 
649   /**
650    * undo the last operation
651    */
652   public void undo()
653   {
654     // System.err.println("==== UNDO");
655     if(undo.canUndo()) undo.undo();
656   }
657 
658   /**
659    * redo the last operation
660    */
661   public void redo()
662   {
663     // System.err.println("==== REDO");
664     if(undo.canRedo()) undo.redo();
665   }
666 
667   /**
668    * Return the anchor position.
669    */
670 
671   public int getAnchor()
672   {
673     return anchor;
674   }
675 
676   /**
677    * Set the anchor postion.
678    * @param offset The new anchor's position
679    */
680 
681   public void setAnchor(int offset)
682   {
683     anchor = offset;
684   }
685 
686   /**
687    * Return the lentgh of the text in the area.
688    */
689 
690   public int getLength()
691   {
692     Document doc=getDocument();
693     return (doc!=null) ? doc.getLength() : 0;
694   }
695 
696   /**
697    * Used for ReplaceAll.
698    * This merges all text changes made between the beginCompoundEdit()
699    * and the endCompoundEdit() calls into only one undo event.
700    */
701 
702   public void beginCompoundEdit()
703   {
704     if (compoundEdit == null)
705       compoundEdit = new CompoundEdit();
706   }
707 
708   /**
709    * See beginCompoundEdit().
710    */
711 
712   public void endCompoundEdit()
713   {
714     if (compoundEdit != null)
715     {
716       compoundEdit.end();
717       if (compoundEdit.canUndo())
718         undo.addEdit(compoundEdit);
719       compoundEdit = null;
720     }
721   }
722 
723   /**
724    * Return the result of a string search.
725    * @param searchStr The string to be found
726    * @param start The search's start offset
727    * @param ignoreCase Set to true, we'll ignore the text case
728    * @return True if <code>searchStr</code> has been found, false otherwise
729    */
730 
731   public boolean find(String searchStr, int start, boolean ignoreCase)
732   {
733     try
734     {
735       if (searchStr.equals("") || searchStr == null) return false;
736       regexp = new RE(searchStr, (ignoreCase == true ? RE.REG_ICASE : 0) | RE.REG_MULTILINE, RESyntax.RE_SYNTAX_PERL5);
737       if (regexp == null)
738       {
739         getToolkit().beep();
740         return false;
741       }
742       String text = getText(start, getLength() - start);
743       REMatch match = regexp.getMatch(text);
744       if (match != null)
745       {
746         select(start + match.getStartIndex(), start + match.getEndIndex());
747         return true;
748       }
749     } catch(Exception e) { }
750     return false;
751   }
752 
753   public boolean replace(String searchStr, String replaceStr,
754        int start, int end, boolean ignoreCase)
755   {
756     boolean r=false;
757     try { r=replaceAll(searchStr,replaceStr,start,end,ignoreCase); }
758     catch(Exception ex) { }
759     return r;
760   }
761 
762   /**
763    * Return the result of a string replace.
764    * @param searchStr The string to be found
765    * @param replaceStr The string which will replace <code>searchStr</code>
766    * @param start The search's start offset
767    * @param end The search's end offset
768    * @param ignoreCase Set to true, we'll ignore the text case
769    * @return True if the replace has been successfully done, false otherwise
770    */
771 
772   public boolean replaceAll(String searchStr, String replaceStr, int start, int end, boolean ignoreCase) throws REException
773   {
774     boolean found = false;
775     beginCompoundEdit();
776     try
777     {
778       if (searchStr.equals("") || searchStr == null) return false;
779       regexp = new RE(searchStr, (ignoreCase == true ? RE.REG_ICASE : 0) | RE.REG_MULTILINE, RESyntax.RE_SYNTAX_PERL5);
780       if (regexp == null)
781       {
782         endCompoundEdit();
783         return false;
784       }
785 
786       if (replaceStr == null)
787         replaceStr = "";
788 
789       REMatch match;
790 
791       Element map = getDocument().getDefaultRootElement();
792       int startLine = map.getElementIndex(start);
793       int endLine = map.getElementIndex(end);
794 
795       for (int i = startLine; i <= endLine; i++)
796       {
797         Element lineElement = map.getElement(i);
798         int lineStart;
799         int lineEnd;
800 
801         if (i == startLine)
802           lineStart = start;
803         else
804           lineStart = lineElement.getStartOffset();
805         if (i == endLine)
806           lineEnd = end;
807         else
808           lineEnd = lineElement.getEndOffset() - 1;
809 
810         lineEnd -= lineStart;
811         String line = getText(lineStart, lineEnd);
812         String newLine = regexp.substituteAll(line, replaceStr);
813         if (line.equals(newLine)) continue;
814         getDocument().remove(lineStart, lineEnd);
815         getDocument().insertString(lineStart, newLine, null);
816 
817         end += (newLine.length() - lineEnd);
818         found = true;
819       }
820     } catch(Exception e) {
821       found = false;
822     }
823     endCompoundEdit();
824     return found;
825   }
826 
827   /**
828    * When an undoable event is fired, we add it to the undo/redo list.
829    */
830 
831   public void undoableEditHappened(UndoableEditEvent e)
832   {
833     if (!getOperation())
834     {
835       if (compoundEdit == null)
836         undo.addEdit(e.getEdit());
837       else
838         compoundEdit.addEdit(e.getEdit());
839     }
840   }
841 
842   /**
843    * When a modification is made in the text, we turn
844    * the 'to_be_saved' flag to true.
845    */
846 
847   public void changedUpdate(DocumentEvent e)
848   {
849     if (!getOperation() && !isDirty()) setDirty();
850   }
851 
852   /**
853    * When a modification is made in the text, we turn
854    * the 'to_be_saved' flag to true.
855    */
856 
857   public void insertUpdate(DocumentEvent e)
858   {
859     if (!getOperation() && !isDirty()) setDirty();
860   }
861 
862   /**
863    * When a modification is made in the text, we turn
864    * the 'to_be_saved' flag to true.
865    */
866 
867   public void removeUpdate(DocumentEvent e)
868   {
869     if (!getOperation() && !isDirty()) setDirty();
870   }
871 
872   /**
873    * Return a String representation of this object.
874    */
875 
876   public String toString()
877   {
878     StringBuffer buf = new StringBuffer();
879     buf.append("BuTextArea: ");
880     buf.append("[filename: " + getCurrentFile() + ";");
881     buf.append(" filesize: " + getLength() + "] -");
882     buf.append("[anchor: " + getAnchor() + "] -");
883     buf.append(" [font-name: " + getFontName() + ";");
884     buf.append(" font-style: " + getFontStyle() + ";");
885     buf.append(" font-size: " + getFontSize() + "]");
886     return buf.toString();
887   }
888 
889 
890   public static void main(String argv[])
891   {
892     JFrame frame=new JFrame("Test BuTextArea");
893     JComponent content=(JComponent)frame.getContentPane();
894     BuTextArea ta1=new BuTextArea();
895     BuTextArea ta2=new BuTextArea();
896     ta2.setDocument(ta1.getDocument());
897 
898     content.setLayout(new GridLayout(1,2));
899     content.add(ta1);
900     content.add(ta2);
901     frame.pack();
902     frame.show();
903     frame.setLocation(200,200);
904   }
905 }
906