import java.awt.*; import java.awt.event.*; import java.awt.datatransfer.*; import java.awt.dnd.*; import java.awt.print.*; import java.io.*; import java.util.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.text.*; /** MTE.java -- MikesTextEditor * * 1000 lines, 33 kBytes. * Has Drag and Drop; has Search and Replace. * Handles EOLs from PC, Mac, Unix seamlessly. * @author M.Lampton UCB SSL 2003, 2011 */ public class MTE { public static void main(String[] args) { javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } private static void createAndShowGUI() { TextEditFrame frame = new TextEditFrame(); // window close is handled by the WindowListener below, not here. frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); frame.setVisible(true); } } /** A frame with a menubar and a text area. * M.Lampton Stellar Software 2003, 2011 */ class TextEditFrame extends JFrame { static private JTextArea myJTA = null; static private SearchDialog mySD = null; static public boolean bDirty = false; static private boolean bWaken = false; static private JFileChooser myJFC = null; static private File file = null; //----Named JMenuItems are accessible to dynamic graying--- static JMenuItem fileNew, fileOpen, fileSave, fileSaveAs, filePrint, fileQuit; static JRadioButtonMenuItem fileDNDappend, fileDNDreplace; static JMenuItem editCut, editCopy, editPaste, editDelete, editFind, editAll; public boolean isDNDappend() // mode queried by Drag-n-Drop { return fileDNDappend.isSelected(); } public void setFile(File f) // New file from DnD { file = f; setTitle(f.getPath()); bDirty = false; } public TextEditFrame() // constructor { setTitle("MikesTextEditor"); setSize(600, 300); setLocation(100, 100); //-----set up JFileChooser and local directory---- myJFC = new JFileChooser(); String sDir = System.getProperty("user.dir"); if (sDir != null) { File fDir = new File(sDir); if ((fDir != null) && (fDir.isDirectory())) myJFC.setCurrentDirectory(fDir); } else myJFC.setCurrentDirectory(null); //---next must come the JTextArea---------- myJTA = new JTextArea(8, 40); myJTA.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); myJTA.setFont(new Font("Courier New", Font.BOLD, 14)); // also line 174 myJTA.setText(""); myJTA.addCaretListener(new MyCaretListener()); // for line number myJTA.getDocument().addDocumentListener(new MyDocumentListener()); myJTA.setSelectionColor(Color.CYAN); //----create local drop target using JTA callback-------- new DropTarget(myJTA, new TextDropTargetListener(this, myJTA)); //---search dialog, visibility coordinated with main window------ mySD = new SearchDialog(this, myJTA); mySD.setVisible(false); addWindowListener(new WindowAdapter() { public void windowIconified(WindowEvent we) { bWaken = mySD.isVisible(); mySD.setVisible(false); } public void windowDeiconified(WindowEvent we) { if (bWaken) mySD.setVisible(true); } public void windowClosing(WindowEvent we) { if (bDirty && !bAbandon()) return; // do not close System.exit(0); // close } }); //------menus: be sure no two accelerators are the same!----- JMenu fileMenu = new JMenu("File"); fileMenu.setMnemonic('F'); fileMenu.addMenuListener(fileGrayingListener); FMIListener fileL = new FMIListener(); fileMenu.add(fileNew = makeItem("New", fileL, 'N', KeyEvent.VK_N)); fileMenu.add(fileOpen = makeItem("Open", fileL, 'O', KeyEvent.VK_O)); fileMenu.add(fileSave = makeItem("Save", fileL, 'S', KeyEvent.VK_S)); fileMenu.add(fileSaveAs = makeItem("SaveAs", fileL, 0, 0)); fileMenu.addSeparator(); ButtonGroup fileBG = new ButtonGroup(); fileMenu.add(fileDNDappend = makeJRBMI("DnD append", null, fileBG, false)); fileMenu.add(fileDNDreplace = makeJRBMI("DnD replace", null, fileBG, true)); fileMenu.addSeparator(); fileMenu.add(filePrint = makeItem("Print", fileL, 'P', KeyEvent.VK_P)); fileMenu.addSeparator(); fileMenu.add(fileQuit = makeItem("Quit", fileL, 'X', KeyEvent.VK_Q)); JMenu editMenu = new JMenu("Edit"); editMenu.setMnemonic('E'); editMenu.addMenuListener(editGrayingListener); EMIListener editL = new EMIListener(); editMenu.add(editCut = makeItem("Cut", editL, 'T', KeyEvent.VK_X)); editMenu.add(editCopy = makeItem("Copy", editL, 'C', KeyEvent.VK_C)); editMenu.add(editPaste = makeItem("Paste", editL, 'P', KeyEvent.VK_V)); editMenu.add(editDelete = makeItem("Delete", editL, 'D', KeyEvent.VK_D)); editMenu.addSeparator(); editMenu.add(editFind = makeItem("Find", editL, 'F', KeyEvent.VK_F)); editMenu.addSeparator(); editMenu.add(editAll = makeItem("SelectAll", editL, 'A', KeyEvent.VK_A)); JMenu fontMenu = new JMenu("Font"); FontListener fontL = new FontListener(); ButtonGroup fontBG = new ButtonGroup(); fontMenu.add(makeJRBMI("10 pt", fontL, fontBG, false)); fontMenu.add(makeJRBMI("12 pt", fontL, fontBG, false)); fontMenu.add(makeJRBMI("14 pt", fontL, fontBG, false)); fontMenu.add(makeJRBMI("16 pt", fontL, fontBG, false)); fontMenu.add(makeJRBMI("10 BOLD", fontL, fontBG, false)); fontMenu.add(makeJRBMI("12 BOLD", fontL, fontBG, false)); fontMenu.add(makeJRBMI("14 BOLD", fontL, fontBG, true)); /// default see line 97 fontMenu.add(makeJRBMI("16 BOLD", fontL, fontBG, false)); JMenu aboutMenu = new JMenu("About"); JMenuItem aboutMenuItem = new JMenuItem("About..."); aboutMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { String sAbout = " Mike's Text Editor\n"; sAbout += "for Java version 1.5.0 and up\n"; sAbout += " www.MikeLampton.com\n\n"; String s = java.lang.System.getProperty("java.version"); sAbout += "host Java version is "+getVersionNumber(s); JOptionPane.showMessageDialog(null, sAbout); } }); aboutMenu.add(aboutMenuItem); //---assemble the menubar------- JMenuBar jmb = new JMenuBar(); jmb.add(fileMenu); jmb.add(editMenu); jmb.add(fontMenu); jmb.add(aboutMenu); setJMenuBar(jmb); //---install the JTextArea------------ Container contentPane = getContentPane(); contentPane.add(new JScrollPane(myJTA)); setVisible(true); } //-------helper methods--------------- String getVersionNumber(String arg) { int length = arg.length(); StringBuffer sb = new StringBuffer(); for (int i=0; i='0') && (c<='9')) || (c=='.') || (c=='_'); } JMenuItem makeItem(String s, ActionListener al, int mnem, int acc) // Convenience routine: assembles a JMI from strings, listener, etc. // Most useful when there is a single listener for an entire menu. // Can create either anonymous or named JMI's. { JMenuItem item = new JMenuItem(s); item.addActionListener(al); item.setActionCommand(s); if (mnem != 0) item.setMnemonic((char) mnem); if (acc != 0) { int iMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); item.setAccelerator(KeyStroke.getKeyStroke(acc, iMask)); } return item; } JRadioButtonMenuItem makeJRBMI(String s, FontListener fl, ButtonGroup bg, boolean active) { JRadioButtonMenuItem item = new JRadioButtonMenuItem(s); item.setSelected(active); item.addActionListener(fl); bg.add(item); return item; } void doPost() // Displays file & line number on titlebar { int caretpos = myJTA.getCaretPosition(); int jline = 1; try { jline = 1 + myJTA.getLineOfOffset(caretpos); } catch (BadLocationException ble) {} setTitle((file==null) ? ""+jline : file.getPath()+" "+jline); } MenuListener fileGrayingListener = new MenuListener() // Sets up graying for file menu items { public void menuSelected(MenuEvent me) { boolean bText = myJTA.getText().length() > 0; fileNew.setEnabled(true); fileOpen.setEnabled(true); fileSave.setEnabled(true); fileSaveAs.setEnabled(bText); filePrint.setEnabled(bText); fileQuit.setEnabled(true); } public void menuDeselected(MenuEvent me) {} public void menuCanceled(MenuEvent me) {} }; MenuListener editGrayingListener = new MenuListener() // Sets up graying for edit menu items { public void menuSelected(MenuEvent me) { boolean bMarked = myJTA.getSelectionEnd() > myJTA.getSelectionStart(); boolean bClip = getClipboardTextLength() > 0; boolean bText = myJTA.getText().length() > 0; editCut.setEnabled(bMarked); editCopy.setEnabled(bMarked); editPaste.setEnabled(bClip); editDelete.setEnabled(bMarked); editFind.setEnabled(bText); editAll.setEnabled(bText); } public void menuDeselected(MenuEvent me) {} public void menuCanceled(MenuEvent me) {} }; //-----clipboard support methods--------------- String getClipboardText() // http://www.exampledepot.com/egs/java.awt.datatransfer/ToClip.html { Transferable t = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null); try { if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) return (String)t.getTransferData(DataFlavor.stringFlavor); } catch (UnsupportedFlavorException e) {} catch (IOException e) {} return null; } int getClipboardTextLength() { String s = getClipboardText(); return (s == null) ? 0 : s.length(); } //------file support methods--------------- void doOpen() // Uses static jfc for file persistence. { int result = myJFC.showOpenDialog(null); if (result == JFileChooser.APPROVE_OPTION) { File f = myJFC.getSelectedFile(); if (f.isFile() && f.canRead()) { try { FileReader fr = new FileReader(f.getPath()); BufferedReader br = new BufferedReader(fr); String s; while ((s = br.readLine()) != null) myJTA.append(s + "\n"); file = f; bDirty = false; } catch (FileNotFoundException fnfe) {} catch (IOException ioe) {} setTitle(file.getPath()); } } } private String getLineText(int lineNumber) { try { int startIndex = myJTA.getLineStartOffset(lineNumber); int endIndex = myJTA.getLineEndOffset(lineNumber); String line = myJTA.getText().substring(startIndex, endIndex - startIndex); return line; } catch (BadLocationException ex) { return ""; } } void doSave() { if (file == null) doSaveAs(); String lines[] = myJTA.getText().split("\\n"); try { PrintWriter pw = new PrintWriter(new FileWriter(file.getPath())); for (int i=0; i -1); } private int iAnalyzeDropFlavor(DropTargetDropEvent event) { Transferable transferable = event.getTransferable(); DataFlavor[] flavors = transferable.getTransferDataFlavors(); for (int i=0; i breaks.length) { return NO_SUCH_PAGE; } Graphics2D g2d = (Graphics2D)g; g2d.translate(xOrigin, yOrigin); int y = 0; int start = (iPage == 0) ? 0 : breaks[iPage-1]; int end = (iPage == breaks.length) ? nlines : breaks[iPage]; for (int line=start; line(); int j=0, istart=0, iend=0; boolean ok = true; do { try { istart = myJTA.getLineStartOffset(j); iend = myJTA.getLineEndOffset(j); String s = myJTA.getText().substring(istart, iend); sList.add(s); ok = true; j++; } catch (BadLocationException ex) { ok = false; } } while (ok); return sList.size(); } private JTextArea myJTA; int breaks[]; // array of page break lines. ArrayList sList; // separate lines of text int nlines = 0; } /** Search+Replace nonmodal Dialog class for JTextArea. * Uses host's WindowListener to disappear when host hides. * M.Lampton Stellar Software 2003, 2011 */ class SearchDialog extends JDialog { public SearchDialog(JFrame givenJF, JTextArea givenJTA) { myJF = givenJF; myJTA = givenJTA; searchJTF = new JTextField("", 10); searchJTF.setMaximumSize(new Dimension(150, 20)); replaceJTF = new JTextField("", 10); replaceJTF.setMaximumSize(new Dimension(150, 20)); setVisible(false); setTitle("Search & Replace"); //-----Panel 1: search panel-------------- JPanel jp1 = new JPanel(); jp1.setPreferredSize(new Dimension(100,20)); jp1.setLayout(new BoxLayout(jp1, BoxLayout.X_AXIS)); JLabel jl1 = new JLabel("Find what?"); jp1.add(Box.createGlue()); jp1.add(jl1); jp1.add(Box.createHorizontalStrut(5)); jp1.add(searchJTF); jp1.add(Box.createHorizontalStrut(50)); //-------Panel 2: replace panel------------- JPanel jp2 = new JPanel(); jp2.setPreferredSize(new Dimension(100,20)); jp2.setLayout(new BoxLayout(jp2, BoxLayout.X_AXIS)); JLabel jl2 = new JLabel("Replace with?"); jp2.add(Box.createGlue()); jp2.add(jl2); jp2.add(Box.createHorizontalStrut(5)); jp2.add(replaceJTF); jp2.add(Box.createHorizontalStrut(50)); //------Panel 3: search direction panel---------- JPanel jp3 = new JPanel(); jp3.setPreferredSize(new Dimension(150, 25)); jp3.setMinimumSize(jp3.getPreferredSize()); dirBG = new ButtonGroup(); JRadioButton fwdJRB = new JRadioButton("forward", true); fwdJRB.setActionCommand("fwd"); fwdJRB.setVerticalTextPosition(AbstractButton.CENTER); fwdJRB.setHorizontalTextPosition(AbstractButton.LEFT); dirBG.add(fwdJRB); JRadioButton revJRB = new JRadioButton("reverse", true); revJRB.setActionCommand("rev"); revJRB.setVerticalTextPosition(AbstractButton.CENTER); revJRB.setHorizontalTextPosition(AbstractButton.RIGHT); dirBG.add(revJRB); jp3.add(Box.createGlue()); jp3.add(Box.createGlue()); jp3.add(fwdJRB); jp3.add(revJRB); jp3.add(Box.createGlue()); //-----Panel 4: case sensitivity panel----------- JPanel jp4 = new JPanel(); jp4.setPreferredSize(new Dimension(150, 25)); jp4.setMinimumSize(jp4.getPreferredSize()); sensBG = new ButtonGroup(); JRadioButton ignoreJRB = new JRadioButton("ignore case", true); ignoreJRB.setActionCommand("ignore"); ignoreJRB.setVerticalTextPosition(AbstractButton.CENTER); ignoreJRB.setHorizontalTextPosition(AbstractButton.LEFT); sensBG.add(ignoreJRB); JRadioButton matchJRB = new JRadioButton("match case", true); matchJRB.setActionCommand("match"); matchJRB.setVerticalTextPosition(AbstractButton.CENTER); matchJRB.setHorizontalTextPosition(AbstractButton.RIGHT); sensBG.add(matchJRB); jp4.add(Box.createGlue()); jp4.add(Box.createGlue()); jp4.add(ignoreJRB); jp4.add(matchJRB); jp4.add(Box.createGlue()); //-----Panel 5: button panel----------------- JPanel jp5 = new JPanel(); jp5.setPreferredSize(new Dimension(150, 40)); jp5.setMinimumSize(jp5.getPreferredSize()); jp5.setLayout(new BoxLayout(jp5, BoxLayout.X_AXIS)); JButton nextButton = new JButton("Next"); nextButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int ix = getNextPosition(false); setTitle((ix >= 0) ? "Found" : "Not found"); } }); JButton replaceButton = new JButton("Replace"); replaceButton.addActionListener(new ActionListener() // must replace possible current selection, and then search to next selection. { public void actionPerformed(ActionEvent e) { int ix = getNextPosition(true); setTitle((ix >= 0) ? "Found" : "Not found"); } }); JButton allButton = new JButton(" All "); allButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int ix=0, n=0; while (ix >= 0) { ix = getNextPosition(true); if (ix >= 0) n++; } setTitle(n + " replacements"); } }); JButton closeButton = new JButton("Close"); closeButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { setVisible(false); } }); jp5.add(Box.createGlue()); jp5.add(nextButton); jp5.add(Box.createGlue()); jp5.add(replaceButton); jp5.add(Box.createGlue()); jp5.add(allButton); jp5.add(Box.createGlue()); jp5.add(closeButton); jp5.add(Box.createGlue()); Container cp = getContentPane(); cp.setLayout(new BoxLayout(cp, BoxLayout.Y_AXIS)); cp.add(Box.createVerticalStrut(5)); cp.add(jp1); cp.add(Box.createVerticalStrut(5)); cp.add(jp2); cp.add(jp3); cp.add(jp4); cp.add(jp5); cp.setPreferredSize(new Dimension(320, 160)); cp.setMinimumSize(getPreferredSize()); pack(); setLocationRelativeTo(myJF); setVisible(false); } int getNextPosition(boolean bReplace) { String sc = myJTA.getSelectedText(); String sr = replaceJTF.getText(); String ss = searchJTF.getText(); String st = myJTA.getText(); int ir = sr.length(); int is = ss.length(); int it = st.length(); if ((is < 1) || (it < 1)) return -1; if (bReplace && (sc!=null) && (sr!=null)) { if (bIgnoreCase()) { sc = sc.toUpperCase(); ss = ss.toUpperCase(); } if (sc.equals(ss)) { myJTA.replaceSelection(sr); // changes text st = myJTA.getText(); // get new text it = st.length(); // get new length } } //----now search for next selection------ if (bIgnoreCase()) { st = st.toUpperCase(); ss = ss.toUpperCase(); } int ix = myJTA.getCaretPosition(); // On repeats, caret is always found at the end of selection if (bForward()) { if (ix == it) // special startup case ix = 0; ix = st.indexOf(ss, ix); // = startNext. or -1. } else { ix -= is + 1; // back up if (ix < is) // absent ix = -1; else ix = st.lastIndexOf(ss, ix); } if (ix >= 0) // success { myJTA.requestFocus(true); myJTA.setSelectionStart(ix); myJTA.setSelectionEnd(ix+is); // Caret must remain at SelectionEnd } return ix; } boolean bIgnoreCase() { String sens = sensBG.getSelection().getActionCommand(); return sens.equals("ignore"); } boolean bForward() { String dir = dirBG.getSelection().getActionCommand(); return dir.equals("fwd"); } JFrame myJF = null; JTextArea myJTA = null; int index = 0; JTextField searchJTF = null; JTextField replaceJTF = null; ButtonGroup dirBG, sensBG; }