Source code: jplot/SimpleEditor.java
1 /*
2 * SimpleEditor -- Originally developped for use by JPlot and JChess,
3 * this class has evolved to a quite useful but still
4 * simple data- and text editor.
5 *
6 * Copyright (C) 2001 Jan van der Lee
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
21 * 02111-1307, USA.
22 *
23 * Send bugs, suggestions or queries to <jplot@cig.ensmp.fr>
24 * The latest releases are found at
25 * http://www.cig.ensmp.fr/~vanderlee/jplot.html
26 *
27 * Initally developed for use by the Centre d'Informatique Geologique
28 * Ecole des Mines de Paris, Fontainebleau, France.
29 */
30
31 package jplot;
32
33 import javax.swing.*;
34 import javax.swing.border.*;
35 import javax.swing.text.*;
36 import javax.swing.filechooser.*;
37
38 import java.io.*;
39 import java.util.*;
40 import java.awt.*;
41 import java.awt.event.*;
42 import java.awt.print.*;
43 import java.net.*;
44
45 /**
46 * The class shows a frame which enables to look at and eventually edit
47 * a file, any type of file. Build in a JPanel, so someone can reuse
48 * the editor in another class.
49 */
50 public class SimpleEditor extends JPanel {
51
52 private JEditorPane textArea;
53 private File file;
54 private JFrame frame;
55 private String text;
56 private JComboBox fonts,sizes;
57 private int size, style;
58 private Font currentFont = new Font("Monospaced",Font.PLAIN,12);
59 private Color currentColor = Color.black;
60 private SmallToggleButton b_bold, b_italic;
61 private JScrollPane scrollpane;
62 private boolean textChanged;
63 private JPanel thisPanel;
64 private JPanel parent;
65 private FindPanel findPanel;
66 private boolean isEditable;
67
68 private Dimension panelSize;
69
70 private final DefaultHighlighter.DefaultHighlightPainter g;
71 //final JobAttributes theJobAttribs = new JobAttributes();
72 //final PageAttributes thePageAttribs = new PageAttributes();
73
74 /**
75 * Principal constructor,
76 * builds the panel which includes the report as text.
77 * @param parent parent
78 * @param title title of the editor
79 * @param idEditable true if the editor is actually editable
80 * @param width width in pixels of the panel
81 * @param height height in pixels of the panel
82 */
83 public SimpleEditor(JPanel parent, String title, boolean isEditable,
84 int width, int height) {
85 setLayout(new BorderLayout());
86 setBorder(new EtchedBorder());
87 size = 12;
88 style = Font.PLAIN;
89 thisPanel = this;
90 this.parent = parent;
91 this.isEditable = isEditable;
92 panelSize = new Dimension(width,height);
93
94 JPanel mainPanel = new JPanel(new BorderLayout());
95 EmptyBorder eb = new EmptyBorder(2,2,2,2);
96 BevelBorder bb = new BevelBorder(BevelBorder.LOWERED);
97 mainPanel.setBorder(new CompoundBorder(eb,bb));
98
99 textArea = new JEditorPane();
100 textArea.setEditable(isEditable);
101 textArea.setFont(currentFont);
102 scrollpane = new JScrollPane(textArea);
103 mainPanel.add(scrollpane,BorderLayout.CENTER);
104
105 JToolBar toolbar = new JToolBar();
106 ImageIcon icon = getImageIcon(parent,"exit.jpg");
107 Action action = new AbstractAction("Close",icon) {
108 public void actionPerformed(ActionEvent e) {
109 frame.dispose();
110 }
111 };
112 addButton(toolbar,action,"Close this panel");
113
114 icon = getImageIcon(parent,"Save24.gif");
115 action = new AbstractAction("Save",icon) {
116 public void actionPerformed(ActionEvent e) {
117 save();
118 }
119 };
120 addButton(toolbar,action,"Save the current file");
121
122 icon = getImageIcon(parent,"Print24.gif");
123 action = new AbstractAction("Print",icon) {
124 public void actionPerformed(ActionEvent e) {
125 printText();
126 }
127 };
128 addButton(toolbar,action,"Print the current content");
129 toolbar.addSeparator();
130
131 icon = getImageIcon(parent,"Refresh24.gif");
132 action = new AbstractAction("Reload",icon) {
133 public void actionPerformed(ActionEvent e) {
134 refresh(file);
135 }
136 };
137 addButton(toolbar,action,"Reload the current file");
138
139 icon = getImageIcon(parent,"Find24.gif");
140 action = new AbstractAction("Find",icon) {
141 public void actionPerformed(ActionEvent e) {
142 find();
143 }
144 };
145 addButton(toolbar,action,"Find a piece of text in this file");
146
147 toolbar.addSeparator();
148 fonts = new JComboBox(JPlot.fontNames);
149 fonts.setMaximumSize(fonts.getPreferredSize());
150 fonts.setEditable(true);
151 fonts.setSelectedItem("Monospaced");
152 fonts.addActionListener(new ActionListener() {
153 public void actionPerformed(ActionEvent e) {
154 setSelectedFont();
155 }
156 });
157 toolbar.add(fonts);
158
159 toolbar.addSeparator();
160 sizes = new JComboBox(new String[] {"7","8","9","10","11","12","14","16","18","20","22","26","32"});
161 sizes.setMaximumSize(sizes.getPreferredSize());
162 sizes.setEditable(true);
163 sizes.setSelectedItem("12");
164 sizes.addActionListener(new ActionListener() {
165 public void actionPerformed(ActionEvent e) {
166 int fontSize = 0;
167 try {
168 fontSize = Integer.parseInt(sizes.getSelectedItem().toString());
169 }
170 catch (NumberFormatException ex) {return;}
171 size = fontSize;
172 setSelectedFont();
173 }
174 });
175 toolbar.add(sizes);
176
177 toolbar.addSeparator();
178 icon = getImageIcon(parent,"Bold16.gif");
179 b_bold = new SmallToggleButton(false,icon,icon,"Bold font");
180 b_bold.addActionListener(new ActionListener() {
181 public void actionPerformed(ActionEvent e) {
182 setSelectedFont();
183 }
184 });
185 toolbar.add(b_bold);
186 b_bold.resetBorder();
187 icon = getImageIcon(parent,"Italic16.gif");
188 b_italic = new SmallToggleButton(false,icon,icon,"Italic font");
189 b_italic.addActionListener(new ActionListener() {
190 public void actionPerformed(ActionEvent e) {
191 setSelectedFont();
192 }
193 });
194 toolbar.add(b_italic);
195 b_italic.resetBorder();
196 mainPanel.add(toolbar,BorderLayout.NORTH);
197
198 add(makeMenuBar(),BorderLayout.NORTH);
199 add(mainPanel,BorderLayout.CENTER);
200
201 textChanged = false;
202 textArea.setSelectionColor(new Color(248,248,215));
203 g = new DefaultHighlighter.DefaultHighlightPainter(new Color(248,248,215));
204
205 frame = new JFrame(title);
206 frame.getContentPane().add(this);
207 frame.pack();
208 }
209
210 /**
211 * Constructor, builds a simple editor/viewer with default dimensions.
212 * @param parent parent
213 * @param title title of the editor
214 * @param idEditable true if the editor is actually editable
215 */
216 public SimpleEditor(JPanel parent, String title, boolean isEditable) {
217 this(parent,title,isEditable,640,550);
218 }
219
220 /**
221 * Add a button of type SmallButton to the toolbar.
222 * Done here to bypass a bug in jdk 1.4 which resets the
223 * border of a button to some default value when added to the
224 * toolbar.
225 */
226 private void addButton(JToolBar toolbar, Action action, String tip) {
227 SmallButton sb = new SmallButton(action,tip);
228 toolbar.add(sb);
229 sb.resetBorder(); // needed to bypass a bug (since jdk1.4)
230 }
231
232 /*
233 * Returns an image which is found in a valid image URL. The basis
234 * of the url is where this class is created.
235 * @param parent the parent class
236 * @param name name of the image
237 * @return an image or icon */
238 private ImageIcon getImageIcon(JPanel parent, String name) {
239 ImageIcon im=null;
240 try {
241 URL imageURL = parent.getClass().getResource("/images/" + name);
242 Toolkit tk = Toolkit.getDefaultToolkit();
243 im = new ImageIcon(tk.createImage(imageURL));
244 }
245 catch(Exception e) {
246 Utils.oops(frame,"Impossible to load the TextEditor's icon '" +
247 name + "'.\nSomething's wrong with the installation.");
248 }
249 return im;
250 }
251
252 /**
253 * Builds the menubar.
254 *
255 * @return an instance of a JMenuBar class
256 */
257 JMenuBar makeMenuBar() {
258 JMenuItem mi;
259 JMenuBar menuBar = new JMenuBar();
260
261 // make the file-menu:
262 JMenu fm = (JMenu) menuBar.add(new JMenu("File"));
263 fm.setMnemonic('F');
264 ImageIcon icon = getImageIcon(parent,"Open16.gif");
265 mi = (JMenuItem) fm.add(new JMenuItem("Open...",icon));
266 mi.setMnemonic('O');
267 mi.addActionListener(new ActionListener() {
268 public void actionPerformed(ActionEvent e) {
269 openFile();
270 }
271 });
272 icon = getImageIcon(parent,"Save16.gif");
273 mi = (JMenuItem) fm.add(new JMenuItem("Save...",icon));
274 mi.setMnemonic('S');
275 mi.addActionListener(new ActionListener() {
276 public void actionPerformed(ActionEvent e) {
277 save();
278 }
279 });
280 icon = getImageIcon(parent,"SaveAs16.gif");
281 mi = (JMenuItem) fm.add(new JMenuItem("Save as...",icon));
282 mi.setMnemonic('A');
283 mi.addActionListener(new ActionListener() {
284 public void actionPerformed(ActionEvent e) {
285 saveAs();
286 }
287 });
288 fm.addSeparator();
289 icon = getImageIcon(parent,"Print16.gif");
290 mi = (JMenuItem) fm.add(new JMenuItem("Print",icon));
291 mi.setMnemonic('P');
292 mi.addActionListener(new ActionListener() {
293 public void actionPerformed(ActionEvent e) {
294 printText();
295 }
296 });
297
298 fm.addSeparator();
299 mi = (JMenuItem) fm.add(new JMenuItem("Quit"));
300 mi.setMnemonic('Q');
301 mi.addActionListener(new ActionListener() {
302 public void actionPerformed(ActionEvent e) {
303 dispose();
304 }
305 });
306
307 // make the edit-menu:
308 //--------------------
309 JMenu edit = (JMenu) menuBar.add(new JMenu("Edit"));
310 edit.setMnemonic('E');
311 icon = getImageIcon(parent,"Refresh16.gif");
312 mi = (JMenuItem) edit.add(new JMenuItem("Reload",icon));
313 mi.setMnemonic('R');
314 mi.addActionListener(new ActionListener() {
315 public void actionPerformed(ActionEvent e) {
316 refresh(file);
317 }
318 });
319 icon = getImageIcon(parent,"Find16.gif");
320 mi = (JMenuItem) edit.add(new JMenuItem("Find",icon));
321 mi.setMnemonic('F');
322 mi.addActionListener(new ActionListener() {
323 public void actionPerformed(ActionEvent e) {
324 find();
325 }
326 });
327 return menuBar;
328 }
329
330 /*
331 * Repaints the text area with the currently selected font.
332 */
333 private void setSelectedFont() {
334 if (b_bold.isSelected()) {
335 if (b_italic.isSelected()) {
336 style = Font.BOLD | Font.ITALIC;
337 }
338 else style = Font.BOLD;
339 }
340 else if (b_italic.isSelected()) style = Font.ITALIC;
341 else style = Font.PLAIN;
342 currentFont = new Font((String)fonts.getSelectedItem(),style,size);
343 textArea.setFont(currentFont);
344 textArea.repaint();
345 }
346
347 /*
348 * Converts the content of the file to a string.
349 */
350 private void fileToString() {
351 StringBuffer sb = new StringBuffer();
352 if (file.exists() && file.canRead()) {
353 try {
354 BufferedReader in = new BufferedReader(new FileReader(file));
355 String s;
356 while ((s=in.readLine()) != null) {
357 sb.append(s).append("\n");
358 }
359 }
360 catch (IOException e) {}
361 }
362 text = sb.toString().trim();
363 }
364
365 /**
366 * Clears the current canvas
367 */
368 public void clear() {
369 try {
370 Document d = textArea.getDocument();
371 d.remove(0,d.getLength());
372 }
373 catch (BadLocationException e) {}
374 }
375
376 /**
377 * Refreshes the text, updates with the content of
378 * the current outputfile.
379 * @param f file containing the report
380 */
381 public void refresh(File f) {
382 //setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
383 file = f;
384 fileToString();
385 refresh(text);
386 //setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
387 }
388
389 /**
390 * Refreshes the text, updates with the content of
391 * the current viewer/editor.
392 * @param text text to display
393 */
394 public void refresh(String s) {
395 if (s.startsWith("<!DOC") || s.startsWith("<!doc") ||
396 s.startsWith("<html>") || s.startsWith("<HTML>")) {
397 textArea.setContentType("text/html");
398 }
399 else textArea.setContentType("text/plain");
400 textArea.setText(s);
401 if (isEditable) textArea.setCaretPosition(0);
402 textArea.revalidate();
403 }
404
405 /**
406 * Saves the text after an edit to the current file.
407 */
408 public void save() {
409 if (file == null) return;
410 try {
411 PrintWriter pw = new PrintWriter(new FileWriter(file));
412 pw.println(textArea.getText());
413 pw.close();
414 }
415 catch (IOException e) {
416 Utils.bummer(frame,"Impossible to write to " + file.toString());
417 }
418 }
419
420 /**
421 * Saves the text after an edit to a new file.
422 */
423 public void saveAs() {
424 JFileChooser chooser = new JFileChooser(new File("."));
425 javax.swing.filechooser.FileFilter ff = new DataFileFilter();
426 chooser.addChoosableFileFilter(ff);
427 chooser.setFileFilter(ff);
428 if (chooser.showDialog(thisPanel,"Save As") == 0) {
429 file = chooser.getSelectedFile();
430 if (file.exists()) {
431 int res = JOptionPane.showConfirmDialog(thisPanel,
432 "The file exists: do you want to overwrite this file?","",JOptionPane.YES_NO_OPTION);
433 if (res == JOptionPane.NO_OPTION) return;
434 }
435 save();
436 }
437 }
438
439 private class DataFileFilter extends javax.swing.filechooser.FileFilter {
440 public boolean accept(File pathname) {
441 return pathname.isDirectory() ||
442 pathname.getName().endsWith(".res") ||
443 pathname.getName().endsWith(".dat");
444 }
445 public String getDescription() {
446 return "data files (*.res, *.dat)";
447 }
448 }
449
450 /**
451 * Saves the text after an edit to a new file.
452 */
453 public void openFile() {
454 JFileChooser chooser = new JFileChooser(new File("."));
455 javax.swing.filechooser.FileFilter ff = new DataFileFilter();
456 chooser.addChoosableFileFilter(ff);
457 chooser.setFileFilter(ff);
458 if (chooser.showOpenDialog(thisPanel) == 0) {
459 if (textChanged) {
460 int result = JOptionPane.showConfirmDialog(thisPanel,"The file has been changed, shouldn't we save it first?");
461 if (result == JOptionPane.CANCEL_OPTION) return;
462 else if (result == JOptionPane.YES_OPTION) save();
463 }
464 refresh(chooser.getSelectedFile());
465 }
466 }
467
468 /**
469 * Disposes the frame. Prefer hide(), but then, show doesn't
470 * work with my current jdk (it pops the frame up iconified).
471 */
472 public void dispose() {
473 clear();
474 //frame.hide();
475 frame.dispose();
476 }
477
478 /*
479 * Print a file. The problem is that this thing only works for
480 * a file, not for a text...
481 */
482 private void printText() {
483 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
484 Thread t = new Thread() {
485 public void run() {
486 PrinterJob job = PrinterJob.getPrinterJob();
487 if (job != null) {
488 PageFormat pf = new PageFormat();
489 PrintTextPainter ptp = new PrintTextPainter(text);
490 ptp.setFont(currentFont);
491 ptp.setForeground(currentColor);
492 job.setPrintable(ptp,pf);
493 job.setCopies(1);
494 if (job.printDialog()) {
495 try {job.print();}
496 catch (Exception e) {
497 System.out.println("Oops, error while printing...");
498 }
499 }
500 }
501 }
502 };
503 t.start();
504 setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
505 }
506
507 /*
508 * Print a file. The problem is that this thing only works for
509 * a file, not for a text...
510 */
511 private void printFile() {
512 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
513 Thread t = new Thread() {
514 public void run() {
515 PrinterJob job = PrinterJob.getPrinterJob();
516 if (job != null) {
517 PageFormat pf = new PageFormat();
518 //PageFormat pf = job.pageDialog(job.defaultPage());
519 //Paper paper = new Paper();
520 //paper.setSize(597,844); // should be an A4
521 //pf.setPaper(paper);
522 PrintFilePainter pfp = new PrintFilePainter(file.toString());
523 pfp.setFont(currentFont);
524 pfp.setForeground(currentColor);
525 job.setPrintable(pfp,pf);
526 job.setCopies(1);
527 if (job.printDialog()) {
528 try {job.print();}
529 catch (Exception e) {}
530 }
531 }
532 }
533 };
534 t.start();
535 setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
536 }
537
538 /**
539 * @return the preferred size of this panel.
540 */
541 public Dimension getPreferredSize() {
542 return panelSize;
543 }
544
545 /**
546 * Pops up a frame with the report printed in it.
547 * @param x x-position of the upper-left corner of the frame.
548 * @param y y-position of the upper-left corner of the frame.
549 * @param f file containing the report
550 */
551 public void show(int x, int y, File f) {
552 refresh(f);
553 frame.setLocation(x,y);
554 frame.setState(Frame.NORMAL);
555 frame.show();
556 }
557
558 /**
559 * Pops up a frame with the report printed in it.
560 * @param x x-position of the upper-left corner of the frame.
561 * @param y y-position of the upper-left corner of the frame.
562 * @param t string with text to display
563 */
564 public void show(int x, int y, String t) {
565 refresh(t);
566 frame.setLocation(x,y);
567 frame.setState(Frame.NORMAL);
568 frame.show();
569 }
570
571 /*
572 * Pops up a find dialog.
573 */
574 private void find() {
575 if (findPanel == null) findPanel = new FindPanel();
576 findPanel.show(frame,100,100);
577 }
578
579 /*
580 * Panel with all the widgets needed to find a string.
581 * Builds a dialog which pops up, containing this panel.
582 */
583 class FindPanel extends GriddedPanel {
584
585 private JDialog dialog;
586 private JTextField textField;
587 private int startIndex=0;
588 private int findIndex;
589 private final Dimension preferredSize = new Dimension(260,80);
590
591 public FindPanel() {
592 JLabel label = new JLabel("Search:");
593 label.setBorder(new EmptyBorder(new Insets(5,5,5,5)));
594 addComponent(label,1,1);
595 textField = new JTextField();
596 textField.setColumns(15);
597 addFilledComponent(textField,1,2);
598 }
599
600 /**
601 * @return the preferred size of this panel.
602 */
603 public Dimension getPreferredSize() {
604 return preferredSize;
605 }
606
607
608 /*
609 * Finds a string in the text. Scrolls to that position and
610 * assures that the string is visible. Wouldn't it be nice
611 * if the string was high-lighted as well...
612 * @param word piece of text which is searched for
613 * @return true if the word was found
614 */
615 private boolean find(String word) {
616 findIndex = -1;
617 int wordLen = word.length();
618 int N = text.length() - wordLen;
619 for(int i=startIndex; i<N; i++) {
620 String s = text.substring(i,i+wordLen);
621 if (s.equals(word)) {
622 findIndex = i;
623 try {
624 textArea.getHighlighter().removeAllHighlights();
625 textArea.getHighlighter().addHighlight(i,i+wordLen,g);
626 }
627 catch(BadLocationException e) {}
628 break;
629 }
630 }
631 return (findIndex > -1)? true : false;
632 }
633
634 /*
635 * Performs the actual search. Asks to rewind the file
636 * if it hits the bottom without finding the string.
637 */
638 private void findAction() {
639 int i=0;
640 if (!textField.getText().equals("")) {
641 startIndex = textArea.getCaretPosition();
642 boolean tryFind = true;
643 while (tryFind) {
644 if (!find(textField.getText())) {
645 int result = JOptionPane.showConfirmDialog(frame,"Reaching the end of the text,\nrestart the scan from the top?","Search failed",JOptionPane.OK_CANCEL_OPTION,JOptionPane.INFORMATION_MESSAGE);
646 if (result == JOptionPane.CANCEL_OPTION) tryFind = false;
647 else startIndex = 0;
648 }
649 else tryFind = false;
650 if (i++ > 0) break;
651 }
652 if (findIndex != -1) {
653 //System.out.println("Found '" + textField.getText() + "' at position " + findIndex);
654 textArea.setCaretPosition(++findIndex);
655 }
656 }
657 }
658
659 /**
660 * Pops up a modal frame including the panel.
661 * @param parent parent frame
662 * @param x x-position of the dialog
663 * @param y y-position of the dialog
664 */
665 public void show(Frame parent, int x, int y) {
666 if (dialog == null) {
667 JPanel panel = new JPanel(new BorderLayout());
668 dialog = new JDialog(parent,"Find",true);
669 dialog.addWindowListener(new WindowAdapter() {
670 public void windowClosing(WindowEvent e) {
671 textArea.getHighlighter().removeAllHighlights();
672 dialog.dispose();
673 }
674 });
675 JPanel p = new JPanel(new FlowLayout());
676 p.setBorder(BorderFactory.createEtchedBorder());
677 JButton b = new JButton("Find");
678 b.addActionListener(new ActionListener() {
679 public void actionPerformed(ActionEvent e) {
680 findAction();
681 }
682 });
683 p.add(b);
684 b = new JButton("Quit");
685 b.addActionListener(new ActionListener() {
686 public void actionPerformed(ActionEvent e) {
687 textArea.getHighlighter().removeAllHighlights();
688 dialog.dispose();
689 }
690 });
691 p.add(b);
692 panel.add(this,BorderLayout.CENTER);
693 panel.add(p,BorderLayout.SOUTH);
694 dialog.getContentPane().add(panel);
695 dialog.setLocation(x,y);
696 dialog.pack();
697 }
698
699 dialog.show(); // blocks until user brings dialog down.
700 }
701 }
702 }