/** * HF Receiver Controller Applet 1.0.0 * @author Robert J Morton YE572246C * @version 25 November 2001, revanped 16 November 2007, Swing Version 31 January 2012, converted to JFrame app Fri 24 Feb 2017 * @copyright Robert J Morton (all rights reserved) */ /* This applet conforms to API 1.1 It does the following: INITIALISATION SEQUENCE 1. Loads a file full of the names of lots of HF broadcasting services and stations from a file located on the server, and places them into a stations Choice list. 2. Selects the first one in the list and loads the list of frequencies on which this station broadcasts into a frequency Choice list. 3. Sets up the standard set of squelch levels S0 to S9+ in a squelch Choice list and selects S5 as the default. 4. Sends the default frequency and squelch level back to the server to be POSTed to a receiver whose command interface is connected to the server PC's AUX port. 5. Awaits user input. USER-INITIATED ACTIONS 1. User can select another station or broadcasting service from the list. The new station's list of frequencies is then loaded from the server into the frequencies Choice list. New station's first frequency is then sent automatically to the receiver as a re-tuning command. 2. User can select a frequency from the frequency Choice list. This frequency is then sent automatically to the receiver as a re-tuning command. 3. User can set the receiver scanning through the current station's frequencies at the rate of one per second. It stops scanning when the receiver detects a signal of a strength equal to or greater than the currently selected squelch level. The user can press the scan Start button again to resume scanning round the frequencies list. 4. User can step up and down the frequency list manually by pressing the Higher and Lower buttons. Each new frequency is sent as a tuning command to the receiver. 5. User can enter a word or phrase and search for it within all station names in the list of stations or braodcasting services. If found, the first station's name is selected and its broadcasting frequencies loaded. Pressing the Find/Next button again proceeds with the search onwards down the list of stations. Appropriate code has to be inserted into this applet where indicated to construct and POST receiver command messages of the type required for your receiver and its control interface software. Your server needs to know where to POST these messages. */ import javax.swing.*; // the Swing GUI system import java.awt.*; // abstract windowing toolkit import java.awt.image.BufferedImage; import java.io.*; // to handle file input/output operations import java.awt.event.*; // for the new-fangled 1.1 event handling import java.net.*; // for downloading data from the remote server public class hfbrx extends JPanel implements Runnable { private static final long serialVersionUID = 117L; // what the hell this is for, I don't know! final hfbrx ap = this; static BufferedImage IMG1; // for applet background world map picture private imgldr il; // instance reference to the image loader private Color bg = new Color(238,238,238); // background colour private boolean ScanFlag = false, // whether or not automatic scanning in progress loadingFreqs = false, // false = stations, true = frequencies StationNamesLoaded = false, // station names not yet loaded FrequenciesLoaded = false, // selected station's frequencies // not yet loaded Init2started = false, Init2finished = false; // whether GUI has finished being built yet private byte B[]; // gigantic byte array to hold the downloaded index data int ls = 0; /* Load Switch says from where to load image and data files: 0 = from local directory 1 = from jar file 2 = from remote server */ private int XE, // horizontal extent of window and JFrame. YE, // vertical extent of window and JFrame. MaxFreq = 0, // max list number for freqs this station transmits on FreqNum = 0, // list number of the current listening frequency lp = 0, LP = 1, // 0=idle 1=connecting 2=loading 3=connect error 4=load error L = 0, // length of the remote item being loaded l = 0, // number of bytes of the above successfully downloaded fh1, Fh1, // font heights and extended font heights (leading) SearchIndex = 0, //list number of station name we have searched up to Aw = 545, // panel width, height and margin Ah = 295, Am = 12, Bw = 80, // button width and height Bh = 30, Mh = 35, // menu height for Choice menus and Text Field Lh = 25, W1 = 100, // width of Station Search field W2 = 245, // width of Station Name Selector W3 = 80, // width of frequency selector W4 = 60, // width of Squelch selector W5 = Aw-W1-Am-Am, // width of button label annotation field X1 = Am, // first column (Station Search) X2 = X1 + W1 + Am, // second column (Station Name Selector) X3 = X2 + W2 + Am, // third column (Frequency) X4 = X3 + W3 + Am, // fourth column (Squelch) X5 = X2 + W1 + 8, // column 2a (button annotations) X7 = X2, // x-coord of start of message field Y1 = Am, // top row (menu annotations) Y2 = Y1 + Am + Am, // Menus row Y3 = Y2 + Mh + Am, // Start button Y4 = Y3 + Bh + Am, // Higher button Y5 = Y4 + Bh + Am, // Lower button Y6 = Y5 + Bh + Am, // Find/Next button Y7 = Y6 + Bh + Am, // y-coord of start of message field Y8 = Y2 + Mh, // y-coord of text under search box W7 = Aw - X2 - Am, // width of the message field y1, // y-origin of the text field and choice box titles y3, // y-origins of the button annotation texts y4, y5, y6, // y-origin of names and values for Station, Frequency and Squelch y7, // y-coord of font base line of message field ns = 0, // total number of stations nf = 0; // total number of frequencies private long si = 1000, // frequency scan interval t, // time at which to step to the next frequency Pn = 505949278; // Pn bias constant private String loadFile = "stations.txt", // name of the station names file cb, // URL - from where go get image/data files SearchString = "", // search string for finding a station name S[] = new String[1000], // allow up to 1000 stations F[] = new String[1000], // allow up to 1000 frequencies per station SquelchLevel = "5"; private worldmap wm; // panel for background image private JTextField FindStn; // text field for station search engine JComboBox stations, // reference for a choice menu for radio station names frequencies, // reference for a choice menu for a station's frequencies squelch; // reference for a choice menu for the squelch levels private JLabel ScanLab, // reference for the 'frequency-scan' button's label NextLab, // reference for the 'Next' button's label PrevLab, // reference for the 'Prev' (previous) button's label FindLab, // reference for the 'Find' button's label SrchLab, // reference for the 'Search' label SvceLab, // reference for the 'Broadcasting Service'label FreqLab, // reference for the 'Frequencies' label SqchLab, // reference for the 'Squelch' label Station; // reference for the 'broadcaster or station.' label private JButton ScanBut, // to start/stop frequency scanning for current station NextBut, // to move to next frequency in a stations frequency list PrevBut, // to move to prev frequency in a stations frequency list FindBut; // to go to the next frequency in the list JButton RtryBut; // for retrying a reload of station names of frequency list // WHITE PANELS AT BOTTOM OF FRAME TO DISPLAY: private stationnamepanel sn; // station name private frequencypanel sf; // frequency private squelchpanel sq; // squelch level private messagespanel sm; // progress and error messages private Font //see constructor method for details font0 = new Font("Serif",Font.PLAIN,12), font1 = new Font("Serif",Font.BOLD,13), font2 = new Font("Serif",Font.BOLD,14); private FontMetrics fm1, fm2; // typeface dimensions for the above fonts private BufferedReader r; // for reading station and frequency lists private InputStream I; // for downloading index of current HTML file private Thread T; // run() thread for this JPanel public hfbrx(int XE, int YE, int ls, String cb) { this.XE = XE; // horizontal [X] dimension of the JPanel this.YE = YE; // vertical [Y] dimension of the JPanel this.ls = ls; // Load Switch this.cb = cb; // code base (URL) of class, images and data files setBounds(0,0,XE,YE); // the JPanel fills the whole of the JFrame area setLayout(null); // JPanel has free-form layout [for graph] setFont(font0); // set font for Text Field, Choice Menus and Buttons fm1 = getFontMetrics(font1); // get letter dimensions for the above font fh1 = fm1.getHeight(); // get font leading Fh1 = fh1 + 2; // to increase the line spacing slightly int y = fm1.getAscent()+(Bh-fh1)/2; // vertical base line offset y1 = Y1 + y; // y-origin of the text field and choice menu titles y3 = Y3 + y; // y-origins of the auto scan button's annotation text y4 = Y4 + y; // y-origin of the 'next' button's annotation text y5 = Y5 + y; // y-origin of the 'prev' button's annotation text fm2 = getFontMetrics(font2); // get letter dimensions for the above font y = fm2.getAscent()+(Bh-fm2.getHeight())/2; // vertical base line offset y6 = Y6 + y; // y origin of the data display text y7 = Y7 + y; // y origin of the data display text /* CREATE TEXT FIELD, MENU AND BUTTON OBJECTS, SIZE THEM AND INSTALL THEM ON APPLET PANEL. Note: the setMaximumRowCount(12) is to stop the drop-down lists from eclipsing the message fields. */ FindStn = new JTextField(); add(FindStn); FindStn.setBounds(X1, Y2, W1, Mh); FindStn.setBackground(bg); stations = new JComboBox(); add(stations); stations.setBounds(X2, Y2, W2, Mh); stations.setMaximumRowCount(12); frequencies = new JComboBox(); add(frequencies); frequencies.setBounds(X3, Y2, W3, Mh); frequencies.setMaximumRowCount(12); // /* Create a new Choice Menu for squelch levels. Set up the 10 menu items. Then set default squench level to 'S5' signal strength. Having done all this, add this choice menu to the JPanel and set the size of the area occuped by the choice menu. */ squelch = new JComboBox(); for(int i = 0; i < 10; i++) squelch.addItem("S" + i); squelch.setSelectedIndex(5); add(squelch); squelch.setBounds(X4, Y2, W4, Mh); squelch.setMaximumRowCount(12); // CREATE LABEL OBJECTS, SIZE THEM AND INSTALL THEM ON APPLET PANEL ScanLab = new JLabel("Automatic Scanning"); add(ScanLab); ScanLab.setBounds(X5, Y3, 400, Bh); NextLab = new JLabel("Listen on next higher frequency."); add(NextLab); NextLab.setBounds(X5, Y4, 400, Bh); PrevLab = new JLabel("Listen on next lower frequency."); add(PrevLab); PrevLab.setBounds(X5, Y5, 400, Bh); SrchLab = new JLabel("Search for..."); add(SrchLab); SrchLab.setBounds(X1, Y1, W1, Lh); SvceLab = new JLabel("Station or Broadcasting Service"); add(SvceLab); SvceLab.setBounds(X2, Y1, W2, Lh); FreqLab = new JLabel("Freq (kHz)"); add(FreqLab); FreqLab.setBounds(X3, Y1, W3+5, Lh); SqchLab = new JLabel("Squelch"); add(SqchLab); SqchLab.setBounds(X4, Y1, W4, Lh); Station = new JLabel("broadcaster
or station."); add(Station); Station.setBounds(X1, Y8, W1, Mh); // CREATE BUTTON OBJECTS, SIZE THEM AND INSTALL THEM ON APPLET PANEL ScanBut = new JButton("Start"); add(ScanBut); ScanBut.setBounds(X2, Y3, W1, Bh); NextBut = new JButton("Higher"); add(NextBut); NextBut.setBounds(X2, Y4, W1, Bh); PrevBut = new JButton("Lower"); add(PrevBut); PrevBut.setBounds(X2, Y5, W1, Bh); FindBut = new JButton("Find/Next"); add(FindBut); FindBut.setBounds(X1, Y6, W1, Bh); RtryBut = new JButton(""); add(RtryBut); RtryBut.setBounds(X1, Y7, W1, Bh); // CREATE AND REGISTER EVENT LISTENERS FOR EACH OF THE ABOVE OBJECTS FindStn.addActionListener(new btlisten(btlisten.FINDSTN, ap)); stations.addItemListener(new chlisten(chlisten.STATION, ap)); frequencies.addItemListener(new chlisten(chlisten.FREQUENCY, ap)); squelch.addItemListener(new chlisten(chlisten.SQUELCH, ap)); ScanBut.addActionListener(new btlisten(btlisten.SCANBUT, ap)); NextBut.addActionListener(new btlisten(btlisten.NEXTBUT, ap)); PrevBut.addActionListener(new btlisten(btlisten.PREVBUT, ap)); FindBut.addActionListener(new btlisten(btlisten.FINDBUT, ap)); RtryBut.addActionListener(new btlisten(btlisten.RTRYBUT, ap)); // CREATE PANELS ON WHICH TO DISPLAY DATA AND MESSAGES sq = new squelchpanel(W4,Bh,y,font2,this,bg); add(sq); sq.setBounds(X4,Y6,W4,Bh); sn = new stationnamepanel(W2,Bh,y,font2,this,bg); add(sn); sn.setBounds(X2,Y6,W2,Bh); sf = new frequencypanel(W3,Bh,y,font2,this,bg); add(sf); sf.setBounds(X3,Y6,W3,Bh); sm = new messagespanel(W7,Bh,y,font2,this,bg); add(sm); sm.setBounds(X7,Y7,W7,Bh); il = new imgldr(this,sm,ls,cb); // create and start image loader t = System.currentTimeMillis() + si; // end of first time frame T = new Thread(this); T.start(); } /* SECOND PHASE OF INITIALISATION: This can only be done after the background image for the applet has finished downloading from the server. Since it involves the creation of a Swing JPanel, it must be executed by the Event Dispatching thread and not the local run() thread from where the process must be invoked. */ private void constructorPhase2() { Init2started = true; // phase 2 initialisation has been scheduled try { javax.swing.SwingUtilities.invokeAndWait( new Runnable(){public void run(){showMap();}} ); } catch(Exception e) { System.out.println("Phase 2 GUI construction failed."); } } /* Create a new instance of the world map background image panel, add it to this JPanel and set its size to the full size of this JPanel. */ void showMap(){ wm = new worldmap(this); add(wm); wm.setBounds(0,0,Aw,Ah); repaint(); // display everything on top of background image lp = 1; // start the station names loading process Init2finished = true; // phase 2 of the initialization has finished } /* THE RUN SECTION. The local thread is started and entered. The local thread T runs around the while() loop. Initially, it runs without sleeping long enough to complete the loading of the applet's background image, the list of station names and the first station's frequency list. Once it has completed these tasks it adopts a cycle of si ( = 1000 ) milliseconds during which it does whatever is necessary (in this case scanning the selected station's next frequency) and sleeping for the rest of the cycle. The thread is terminated by the stop() method, which is called by the host environment when the applet is terminated. */ public void run() { while(T != null){ // while this thread is alive runLoop(); // get time remaining in this scan interval long s = t - System.currentTimeMillis(); if(s < 50) s = 50; // in case host PC is too slow try { // allow external events to break the thread Thread.sleep(s); // sleep for remainder of time frame } catch (InterruptedException e) { } // set finish time of next time frame t = System.currentTimeMillis() + si; } } private void runLoop() { // re-entered every 'si' milliseconds if(!il.imagesLoaded()) return; // media tracker not yot terminated if(!Init2finished) { // if phase 2 of construction not yet finished if(!Init2started) // If phase 2 of construction not yet started constructorPhase2(); // initiate construction phase 2 return; } sq.repaint(); /* if in FREQUENCY SCAN mode and this station's frequencies have been loaded, go to the next higher frequency in the station's list */ if(ScanFlag && FrequenciesLoaded) nextFreq(); /* if new data download requesthas been initiated, connect to index resource on server. Otherwise, if in file download- ing phase, continue to manage the downloading of its content. */ if(lp == 1) fileConnect(); else if(lp == 2) fileLoad(); /* If loading of station data has finished then clear the "loading" message if it has not already been cleared, then latch the current load state. */ if(lp != LP) { if(lp == 0) sm.showMsg("",false); LP = lp; } } // Try to reload images in the event that loading failed the first time. void imgReload(){ il.killTracker(); il = new imgldr(this,sm,ls,cb); } /* THE DOWNLOADING OF STATION NAMES AND FREQUENCIES Station names are loaded automatically after phase 1 and pahse 2 of the applet's initialisation have completed and the applet's background image has been installed. The frequency list for the first station in the stations list is then downloaded. The frequency list of another station is downloaded as and when that other station is selected by the user from the station names Choice menu. The station names loading process comprises 3 phases. fileConnect() establishes an HTTP connection between the applet and the station names file on the server. The fileLoad() then loads the byte stream of the file into a byte array B[]. Once all the bytes of the file are in B[], they are the unravel() method parses the byte stream into the list of station names and puts them in the String array S[]. The installStations() method then copies the station names from S[] into the items list of the station selector JComboBox. This has to be done through an invokation call to the Event Dispatching thread to avoid upsetting the JComboBox. Essentially the same phases occur in downloading and installing the frequency list for a given station. The only difference is that in this case the first phase is preluded by the setFreqFileName() method, which sets up the file name of the frequency list for the particular station selected. */ // form the file name of the selected station's frequencies file private void setFreqFileName(int x) { loadFile = "" + x; // form string version of station number if(x < 10) // if it is less than 10 loadFile = "00" + loadFile; // pad it out with 2 leading zeros else if(x < 100) // else if it is less than 100 loadFile = "0" + loadFile; // pad it with just 1 leading zero // form the name of the appropriate frequencies file loadFile = "freqs" + loadFile + ".txt"; loadingFreqs = true; // state type of file being loaded FrequenciesLoaded = false; // selected station's freqs not yet loaded sf.setState(false); // inform the frequency display panel of this sn.setStn(x); // send station name to station display panel lp = 1; // start the loading process } // CONNECT TO THE APPROPRIATE DATA FILE ON THE SERVER private void fileConnect() { sm.showMsg("Connecting to server...",false); // true = normal message, false = not image load try { // set to capture any exceptions locally switch(ls) { case 0: L = (int)(new File(loadFile)).length(); I = new FileInputStream(loadFile); break; case 1: // Create an input stream to load ile from jar file L = 4096; // default maximum content length I = getClass().getResourceAsStream(loadFile); break; case 2: // Create an input stream to load ile from server URLConnection u = new URL(cb + loadFile).openConnection(); I = u.getInputStream(); L = u.getContentLength(); } B = new byte[L]; // create the gigantic buffer for the data l = 0; // number of bytes so far successfully downloaded lp = 2; // advance to the index loading phase } /* If any exception at all occurs, note what kind of exception it was and where it occurred. */ catch(Exception e) { System.out.println("fileConnect() " + e); if(loadingFreqs) sm.showMsg("Couldn't find this station's frequencies.", true); else sm.showMsg("Couldn't find station names.", true); lp = 3; // reset loader to its idle state } } private void fileLoad() { // DOWNLOAD THE APPROPRIATE DATA FILE if(loadingFreqs) sm.showMsg("Loading frequencies...", false); else sm.showMsg("Loading station names...", false); int k; // current byte being read() try { if(ls == 1) { // L is unknown for jar files /* While the entire file has not yet been down- loaded, add each new byte to the big byte array. */ while((k=I.read()) != -1) B[l++] = (byte)k; L = l; } else // else, L is known for local files and server connections while(l 2) { RtryBut.setText(""); lp = 1; LP = 1; } } /* STEP TO NEXT FREQUENCY IN STATION'S FREQUENCY LIST: Provided station's frequencies have finished loading, increment frequency number, looping back to zero if overshot and send frequency to freqency display panel. */ private void nextFreq() { if(!FrequenciesLoaded) return; if(++FreqNum > MaxFreq) FreqNum = 0; sf.setFreq(FreqNum); } // START OR RESUME A SEARCH FROM A GIVEN SEARCH INDEX VALUE: void findStation() { if(ScanFlag) // if in automatic scanning mode return; // bail out immediately /* get the search string that is currently in the text field trim out all excess space-characters and set all characters to lower case. */ String s = FindStn.getText().trim().toLowerCase(); /* if different from last time's search string, update search string and reset search index to start of list. */ if(!SearchString.equals(s)) { SearchString = s; SearchIndex = 0; } int I = stations.getItemCount(), // number of station names in the list i = SearchIndex; // item after where we got to last time /* For as many stations as there are in the list, get the next station name. If the search string is found to be contained within this station name, then select this station name, mark from where to start searching next time, clear possible 'not found' message from last time, select this station and then bail out. */ for(int k = 0; k < I; k++) { s = stations.getItemAt(i); s = s.toLowerCase(); if(s.indexOf(SearchString) != -1) { stations.setSelectedIndex(i); SearchIndex = i + 1; System.out.println(""); selectStation(); return; } else if(++i >= I) // else increment station's index number i = 0; // looping back to zero if necessary } System.out.println("Search string not found in any station name."); } } /* The following classes create instances of ActionListener. They used by this applet to create and service action events from the Text Field, the Choice Menus and each of the Control Buttons. The underlying system calls the actionPerformed() method below every time a button is pushed. The method is invoked upon the instance of the following class corresponding to that particular button. The value of the variable id at the time is therefore that which corresponds to the button concerned. This causes the appropriate case statement to be executed, thus passing control to the appropriate handling method above. */ // LISTENS FOR EVENTS FROM THE CHOICE MENU SELECTORS class chlisten implements ItemListener { static final int STATION = 0, // an event from the 'Stations' Choice Menu FREQUENCY = 1, // an event from the 'Frequency' Choice Menu SQUELCH = 2; // an event from the 'Squelch' Choice Menu int id; // one of the above events hfbrx ap; // application that called: always the above applet! // constructor for a new Choice selection event public chlisten(int id, hfbrx ap) { this.id = id; // set id number for this instance of 'chlisten' this.ap = ap; //set reference to the instance of 'hfbrx' applet } // from which it came. (there will only be one instance) // a Choice selection has occurred from checkbox 'id' public void itemStateChanged(ItemEvent e) { switch(id) { case STATION: ap.selectStation(); // a station was selected break; case FREQUENCY: ap.selectFrequency(); // a frequency was selected break; case SQUELCH: ap.selectSquelch(); // a squelch level was selected break; } } } // LISTENS FOR EVENTS FROM THE BUTTONS class btlisten implements ActionListener { static final int SCANBUT = 0, // 'scan button pressed' event NEXTBUT = 1, // 'next button pressed' event PREVBUT = 2, // 'prev button pressed' event FINDSTN = 3, // 'station search text field' event FINDBUT = 4, // 'station search button pressed' event RTRYBUT = 5; // 'station search button pressed' event int id; // one of the above protected by YE572246C hfbrx ap; // the application that called: always the above applet! public btlisten(int id, hfbrx ap) { // constructor for a new command this.id = id; this.ap = ap; } // one of the buttons has been clicked public void actionPerformed(ActionEvent e) { switch(id) { // id of this instance of ActionEvent case SCANBUT: ap.pressScanBut(); break; // it was the 'scan' button case NEXTBUT: ap.pressNextBut(); break; // it was the 'next' button case PREVBUT: ap.pressPrevBut(); break; // it was the 'prev' button case FINDSTN: ap.findStation(); break; // it was the 'find' field C/R case FINDBUT: ap.findStation(); break; // it was the 'find' button case RTRYBUT: ap.pressRtryBut(); // it was the 'find' button } } }