import gnu.regexp.*;
import java.awt.*;
import java.applet.*;
import sun.audio.*;
import java.util.*;

/*

--------------------------------------------------------------------------------

Morse Code Translator Java Applet
Copyright (C) 1999-2004  Stephen C Phillips

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

--------------------------------------------------------------------------------

You can contact me by email at: morse@scphillips.com

--------------------------------------------------------------------------------

Version 1.2.0   2004-08-09
Added the at sign and equals sign.  Added my name to the applet display(!)
Version 1.1.0	2001-06-28
Added a few more playback speeds
Version 1.0.1	1999-08-23
Minor correction to translate action button
Version 1.0.0	1999-08-10
First public release

--------------------------------------------------------------------------------

*/

    public class Morse extends Applet {
        byte [] sample = null;
        AudioData playable = null;
        private Button playButton;
        private Button transButton;
        private TextArea input;
        private Choice wpmChoice;
        private TextArea output;
        private int wpm = 15;
        private Hashtable hMorse;
        private Hashtable hText;
        private String morse;
        private String text;

        public void init() {
            setBackground(Color.lightGray);
            Font butFont = new Font("Helvetica", Font.PLAIN, 10);

            GridBagLayout gbag = new GridBagLayout();
            setLayout(gbag);
            GridBagConstraints con = new GridBagConstraints();

            Label l4 = new Label("Morse Code Translator", Label.CENTER);
            l4.setFont(new Font("Helvetica", Font.BOLD, 16));
            con.gridx = 0;
            con.gridy = 0;
            con.gridwidth = 6;
            con.anchor = GridBagConstraints.CENTER;
            //con.fill = GridBagConstraints.HORIZONTAL;
            con.weightx = 1.0;
            gbag.setConstraints(l4, con);
            add(l4);

            Label l2 = new Label("Input", Label.CENTER);
            l2.setFont(new Font("Helvetica", Font.BOLD, 12));
            con.gridx = 0;
            con.gridy = 1;
            gbag.setConstraints(l2, con);
            add(l2);

            input = new TextArea(2, 80);
            input.setEditable(true);
            input.setFont(new Font("Courier", Font.PLAIN, 10));
            con.gridx = 0;
            con.gridy = 2;
            gbag.setConstraints(input, con);
            add(input);

            Label l3 = new Label("Translation", Label.CENTER);
            l3.setFont(new Font("Helvetica", Font.BOLD, 12));
            con.gridx = 0;
            con.gridy = 3;
            gbag.setConstraints(l3, con);
            add(l3);

            output = new TextArea(2, 80);
            output.setEditable(false);
            output.setFont(new Font("Courier", Font.PLAIN, 10));
            con.gridx = 0;
            con.gridy = 4;
            gbag.setConstraints(output, con);
            add(output);

            transButton = new Button("Translate");
            transButton.setFont(butFont);
            transButton.setForeground(Color.black);
            transButton.setBackground(Color.lightGray);
            con.gridx = 0;
            con.gridy = 5;
            con.gridwidth = 3;
            con.ipadx = 2;
            con.ipady = 2;
            con.insets = new Insets(6,0,0,0);
            gbag.setConstraints(transButton, con);
            add(transButton);

            Panel p1 = new Panel();
            GridBagLayout gbag1 = new GridBagLayout();
            p1.setLayout(gbag1);

            con.gridx = 3;
            con.gridy = 5;
            con.gridwidth = 3;
            con.ipadx = 0;
            con.ipady = 0;
            gbag.setConstraints(p1, con);
            add(p1);

            //sub panel
            playButton = new Button("Play");
            playButton.setFont(butFont);
            playButton.setForeground(Color.black);
            playButton.setBackground(Color.lightGray);
            con.gridx = 0;
            con.gridy = 0;
            con.gridwidth = 1;
            con.ipadx = 2;
            con.ipady = 2;
            con.insets = new Insets(0,0,0,0);
            gbag1.setConstraints(playButton, con);
            p1.add(playButton);

            Label l5 = new Label("at");
            l5.setFont(butFont);
            con.gridx = 1;
            con.gridy = 0;
            con.ipadx = 0;
            con.ipady = 0;
            gbag1.setConstraints(l5, con);
            p1.add(l5);

            wpmChoice = new Choice();
            wpmChoice.setFont(butFont);
            wpmChoice.addItem("5");
            wpmChoice.addItem("10");
            wpmChoice.addItem("13");
            wpmChoice.addItem("15");
            wpmChoice.addItem("16");
            wpmChoice.addItem("17");
            wpmChoice.addItem("18");
            wpmChoice.addItem("19");
            wpmChoice.addItem("20");
            wpmChoice.addItem("25");
            wpmChoice.addItem("30");
            wpmChoice.addItem("35");
            wpmChoice.addItem("40");
            wpmChoice.select(3);
            wpmChoice.setForeground(Color.black);
            wpmChoice.setBackground(Color.lightGray);
            con.gridx = 2;
            con.gridy = 0;
            gbag1.setConstraints(wpmChoice, con);
            p1.add(wpmChoice);

            Label l1 = new Label("WPM");
            l1.setFont(butFont);
            con.gridx = 3;
            con.gridy = 0;
            gbag1.setConstraints(l1, con);
            p1.add(l1);

            Label l6 = new Label("By Stephen C Phillips (www.scphillips.com)", Label.CENTER);
            l6.setFont(new Font("Helvetica", Font.BOLD, 12));
            con.gridx = 0;
            con.gridy = 6;
            con.gridwidth = 6;
            con.anchor = GridBagConstraints.CENTER;
            gbag.setConstraints(l6, con);
            add(l6);

            hMorse = new Hashtable(46);
            hText = new Hashtable(46);

            hMorse.put(new Character('A'), ".-");
            hMorse.put(new Character('B'), "-...");
            hMorse.put(new Character('C'), "-.-.");
            hMorse.put(new Character('D'), "-..");
            hMorse.put(new Character('E'), ".");
            hMorse.put(new Character('F'), "..-.");
            hMorse.put(new Character('G'), "--.");
            hMorse.put(new Character('H'), "....");
            hMorse.put(new Character('I'), "..");
            hMorse.put(new Character('J'), ".---");
            hMorse.put(new Character('K'), "-.-");
            hMorse.put(new Character('L'), ".-..");
            hMorse.put(new Character('M'), "--");
            hMorse.put(new Character('N'), "-.");
            hMorse.put(new Character('O'), "---");
            hMorse.put(new Character('P'), ".--.");
            hMorse.put(new Character('Q'), "--.-");
            hMorse.put(new Character('R'), ".-.");
            hMorse.put(new Character('S'), "...");
            hMorse.put(new Character('T'), "-");
            hMorse.put(new Character('U'), "..-");
            hMorse.put(new Character('V'), "...-");
            hMorse.put(new Character('W'), ".--");
            hMorse.put(new Character('X'), "-..-");
            hMorse.put(new Character('Y'), "-.--");
            hMorse.put(new Character('Z'), "--..");
            hMorse.put(new Character('1'), ".----");
            hMorse.put(new Character('2'), "..---");
            hMorse.put(new Character('3'), "...--");
            hMorse.put(new Character('4'), "....-");
            hMorse.put(new Character('5'), ".....");
            hMorse.put(new Character('6'), "-....");
            hMorse.put(new Character('7'), "--...");
            hMorse.put(new Character('8'), "---..");
            hMorse.put(new Character('9'), "----.");
            hMorse.put(new Character('0'), "-----");
            hMorse.put(new Character('.'), ".-.-.-");
            hMorse.put(new Character(','), "--..--");
            hMorse.put(new Character(':'), "---...");
            hMorse.put(new Character('?'), "..--..");
            hMorse.put(new Character('\''), ".----.");
            hMorse.put(new Character('-'), "-....-");
            hMorse.put(new Character('/'), "-..-.");
            hMorse.put(new Character('('), "-.--.-");
            hMorse.put(new Character(')'), "-.--.-");
            hMorse.put(new Character('"'), ".-..-.");
            hMorse.put(new Character('@'), ".--.-.");
            hMorse.put(new Character('='), "-...-");
	
            Enumeration list = hMorse.keys();
            while (list.hasMoreElements()) {
                Character c = (Character)list.nextElement();
                String s = (String)hMorse.get(c);
                hText.put(s,c);
            }
        }

        public void paint(Graphics g) {
            g.drawRect(0,0,bounds().width-1, bounds().height-1);
        }
    
        public boolean action(Event event, Object arg) {
            if (event.target == transButton) {
                if (isText(input.getText())) {
                    text = input.getText();
                    morse = toMorse(text);
                    output.setText(morse);
                } else {
                    morse = input.getText();
                    text = toText(morse);
                    output.setText(text);
                }
                return true;
            }
            if (event.target == playButton) {
                //Applet.this.showStatus(status);
                if (isText(input.getText())) {
                    text = input.getText();
                    morse = toMorse(text);
                    output.setText(morse);
                    makeSample(morse);
                } else {
                    morse = input.getText();
                    text = toText(morse);
                    output.setText(text);
                    makeSample(input.getText());  //using the processed morse
                }
                playSample();
                return true;
            }
            else if (event.target == wpmChoice) {
                if (arg.equals("5")) wpm = 5;
                else if (arg.equals("10")) wpm = 10;
                else if (arg.equals("13")) wpm = 13;
                else if (arg.equals("15")) wpm = 15;
                else if (arg.equals("16")) wpm = 16;
                else if (arg.equals("17")) wpm = 17;
                else if (arg.equals("18")) wpm = 18;
                else if (arg.equals("19")) wpm = 19;
                else if (arg.equals("20")) wpm = 20;
                else if (arg.equals("25")) wpm = 25;
                else if (arg.equals("30")) wpm = 30;
                else if (arg.equals("35")) wpm = 35;
                else if (arg.equals("40")) wpm = 40;
                //wpm = (event.target.getSelectedIndex() + 1) * 5;
                return true;
            }
            else return super.action(event, arg);
        }

        /**************************************************************/
        /*  Text Processing                                          */
        /**************************************************************/

        public boolean isText(String sText) {
            boolean matches = false;
            try {
                RE morseRE = new RE("^[._/ \\|-]*$");
                matches = morseRE.isMatch(sText);
            } 
            catch (REException arg) { }
            return !matches;
        }

        public String toMorse(String sText) {
            /*
              tr/a-z/A-Z/; #lowercase
              tr/ / /s; #sqeeze spaces
              s/^ *(.*?) *$/$1/; #chop start and end of ' '
              s# #@ #g; #mark word boundaries (with non-Morse character)
              s#([A-Z0-9.,:?'-/()"])#$toMorse{$1}.' '#eg; #put Morse in
              s#@#/#g; #re-mark word boundaries
            */

            StringBuffer sbMorse = new StringBuffer();

            sText = sText.toUpperCase();
            try {
                RE sub1 = new RE("\\s+");
                RE sub2 = new RE("^\\s+");
                RE sub3 = new RE("\\s+$");
                //RE sub4 = new RE(" ");
                RE sub5 = new RE("[^A-Z0-9.,?\'\"/() -=@]");
                sText = sub1.substituteAll(sText, " ");
                sText = sub2.substitute(sText, "");
                sText = sub3.substitute(sText, "");
                sText = sub5.substituteAll(sText, "");
                input.setText(sText);

                for (int i = 0; i < sText.length(); i++) {
                    char c = sText.charAt(i);
                    Character ch = new Character(c);
                    if (Character.isSpace(c)) {
                        sbMorse.append("/ ");
                    } else {
                        sbMorse.append(hMorse.get(ch));
                        sbMorse.append(' ');
                    }
                }
            } catch (REException arg) {
                //input.setText(arg.getMessage());
                input.setText("error");
            }

            sText = sbMorse.toString();
            return sText;
        }

        public String toText(String sMorse) {
            /*
              tr/ / /s; #sqeeze space
              s#( ?/ ?)+# /#g; #deal with multiple and space-padded '/'
              s#^[ /]*##; #chop start of '/' and ' '
              s#[ /]*$# #; #chop end of same, leaving one ' '
              s#/#@#g; #mark word boundaries (with non-Morse character)
              s/([-.]+) /$fromMorse{"$1"}/eg; #put in text
              s/@/ /g; #replace word boundaries
              s#\(([^(]*)\(#\($1\)#g; #match brackets
            */
            StringBuffer sbText = new StringBuffer();
            String letter = new String();

            try {
                RE sub1 = new RE("\\|");  // for '/'
                RE sub2 = new RE("_");  // for '-'
                RE sub3 = new RE("\\s+");  // for ' '
                RE sub4 = new RE("( ?/ ?)+");  // for ' / '
                RE sub5 = new RE("^[ /]*");  // for ''
                RE sub6 = new RE("[ /]*$");  // for ''
                sMorse = sub1.substituteAll(sMorse, "/");
                sMorse = sub2.substituteAll(sMorse, "-");
                sMorse = sub3.substituteAll(sMorse, " ");
                sMorse = sub4.substituteAll(sMorse, " / ");
                sMorse = sub5.substitute(sMorse, "");
                sMorse = sub6.substitute(sMorse, "");
                input.setText(sMorse);
                sMorse = sMorse + " ";

                RE sub7 = new RE("^.*? ");  // for ''  (rest of string)
                RE sub8 = new RE(" .*$");  // for ''  (first word)

                while (sMorse.length() != 0) {
                    letter = sub8.substitute(sMorse, "");
                    sMorse = sub7.substitute(sMorse, "");
                    if (letter.equals("/")) {
                        sbText.append(" ");
                    } else if (hText.get(letter) == null) {
                        sbText.append("*");
                    } else {
                        sbText.append(hText.get(letter));
                    }
                }
                // need to do parentheses matching!

            } catch (REException arg) {
                //input.setText(arg.getMessage());
                input.setText("error");
            }

            sMorse = sbText.toString();
            return sMorse;
        }

        /**************************************************************/
        /*  Sound Processing                                          */
        /**************************************************************/

        public void playSample() {
            AudioDataStream stream = new AudioDataStream(playable);
            AudioPlayer.player.start(stream);
        }

        public void makeSample(String morse) {
            char c;
            int dits=0, dahs=0, spcs=0, slashes=0;
            int dahLen = 3;
            int pause1 = 1;
            int pause2 = 3;
            int pause3 = 7;
            int wordLen = 50;
            int ditLen = (int)1000*60/(wpm*wordLen);  
            //thousandths of a sec per dit, or 8-byte frames per dit

            for (int i = 0; i < morse.length(); i++) {
                c = morse.charAt(i);
                //status += c;
                switch (c) {
                case '.': 
                    dits++; 
                    break;
                case '-': 
                    dahs++; 
                    break;
                case ' ': 
                    spcs++; 
                    break;
                case '/': 
                    slashes++; 
                    break;
                    //need default
                }
            }
            //status += " "+dits+" "+dahs+" "+spcs+" "+slashes;
            int length = ditLen*8*(dits*(1+pause1)+dahs*(dahLen+pause1)+spcs*(pause2-pause1)+slashes*(pause3-pause1));
	
            sample = new byte[length];
	
            int sPos = 0;
            for (int i = 0; i < morse.length(); i++) {
                c = morse.charAt(i);
                switch (c) {
                case '.': 
                    addSound(1, sPos, ditLen);
                    sPos += (1+pause1)*ditLen*8;
                    break;
                case '-': 
                    addSound(dahLen, sPos, ditLen);
                    sPos += (dahLen+pause1)*ditLen*8;
                    break;
                case ' ': 
                    sPos += (pause2-pause1)*ditLen*8;
                    break;
                case '/': 
                    sPos += (pause3-pause1)*ditLen*8;
                    break;
                }
            }
            playable = new AudioData(sample);
        }

        public void addSound(int units, int sPos, int ditLen) {
            for (int i = 0; i < units*ditLen; i++) {
                sample[sPos+i*8] = (byte)0xA7;
                sample[sPos+i*8+1] = (byte)0x81;
                sample[sPos+i*8+2] = (byte)0xA7;
                sample[sPos+i*8+3] = (byte)0;
                sample[sPos+i*8+4] = (byte)0x59;
                sample[sPos+i*8+5] = (byte)0x7F;
                sample[sPos+i*8+6] = (byte)0x59;
                sample[sPos+i*8+7] = (byte)0;
            }
        }
    }
