001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas/
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.control;
030
031import java.awt.Color;
032import java.awt.Component;
033import java.awt.Dimension;
034import java.awt.Font;
035import java.awt.FontMetrics;
036import java.awt.GridBagConstraints;
037import java.awt.GridBagLayout;
038import java.awt.Image;
039import java.awt.event.ActionEvent;
040import java.awt.event.ActionListener;
041import java.awt.event.ItemEvent;
042import java.awt.event.ItemListener;
043import java.awt.event.KeyAdapter;
044import java.awt.event.KeyEvent;
045import java.awt.event.MouseAdapter;
046import java.awt.event.MouseEvent;
047import java.io.BufferedReader;
048import java.io.DataInputStream;
049import java.io.InputStreamReader;
050import java.net.URLEncoder;
051import java.rmi.RemoteException;
052import java.util.ArrayList;
053import java.util.Hashtable;
054import java.util.List;
055import java.util.StringTokenizer;
056
057import javax.swing.BorderFactory;
058import javax.swing.JCheckBox;
059import javax.swing.JComponent;
060import javax.swing.JLabel;
061import javax.swing.JPanel;
062import javax.swing.JScrollBar;
063import javax.swing.JScrollPane;
064import javax.swing.JTabbedPane;
065import javax.swing.JTextField;
066import javax.swing.JTextPane;
067import javax.swing.ScrollPaneConstants;
068import javax.swing.SwingUtilities;
069import javax.swing.border.EmptyBorder;
070import javax.swing.text.BadLocationException;
071import javax.swing.text.Style;
072import javax.swing.text.StyleConstants;
073import javax.swing.text.StyledDocument;
074
075import visad.VisADException;
076import visad.georef.MapProjection;
077
078import ucar.unidata.data.DataChoice;
079import ucar.unidata.data.DataContext;
080import ucar.unidata.data.DataSource;
081import ucar.unidata.data.DataSourceImpl;
082import ucar.unidata.idv.ControlContext;
083import ucar.unidata.idv.IntegratedDataViewer;
084import ucar.unidata.idv.MapViewManager;
085import ucar.unidata.idv.control.ControlWidget;
086import ucar.unidata.idv.control.ImageSequenceControl;
087import ucar.unidata.idv.control.WrapperWidget;
088import ucar.unidata.ui.colortable.ColorTableManager;
089import ucar.unidata.util.ColorTable;
090import ucar.unidata.util.GuiUtils;
091import ucar.unidata.util.Misc;
092
093import edu.wisc.ssec.mcidasv.data.FrameDirtyInfo;
094import edu.wisc.ssec.mcidasv.data.McIdasFrame;
095import edu.wisc.ssec.mcidasv.data.McIdasXDataSource;
096import edu.wisc.ssec.mcidasv.data.McIdasXInfo;
097import edu.wisc.ssec.mcidasv.ui.McIdasFrameDisplay;
098
099/**
100 * A DisplayControl for handling McIDAS-X image sequences
101 */
102public class McIdasImageSequenceControl extends ImageSequenceControl {
103        
104    private JLabel runningThreads;
105    private JCheckBox navigatedCbx;
106    private JPanel frameNavigatedContent;
107    private McIdasFrameDisplay frameDisplay;
108    private Dimension frameSize;
109    private JTextField inputText;
110    private JScrollPane outputPane;
111    private StyledDocument outputText;
112    private Font outputFont = new Font("Monospaced", Font.BOLD, 12);
113    private ArrayList commandHistory = new ArrayList();
114    private int commandHistoryIdx = -1;
115    private boolean commandHistoryMode = true;
116    
117    /** McIDAS-X handles */
118    private McIdasXInfo mcidasxInfo;
119    private McIdasXDataSource mcidasxDS;
120
121    private int threadCount = 0;
122
123    private static DataChoice dc=null;
124    private static Integer frmI;
125
126    /** Holds frame component information */
127    private FrameComponentInfo frameComponentInfo;
128    private List frameDirtyInfoList = new ArrayList();
129    private List frameNumbers = new ArrayList();
130
131    /**
132     * Default ctor; sets the attribute flags
133     */
134    public McIdasImageSequenceControl() {
135        setAttributeFlags(FLAG_COLORTABLE | FLAG_DISPLAYUNIT);
136        initFrameComponentInfo();
137        this.mcidasxInfo = null;
138        this.mcidasxDS = null;
139        
140        setDisplayId("bridgecontrol");
141        setHelpUrl("idv.controls.bridgecontrol");
142        
143        setExpandedInTabs(true);
144    }
145
146    /**
147     * Creates, if needed, the frameComponentInfo member.
148     */
149    private void initFrameComponentInfo() {
150        if (this.frameComponentInfo == null) {
151            this.frameComponentInfo = new FrameComponentInfo(true, true, true, false, true, false);
152        }
153    }
154    
155    /**
156     * Initializes the frameDirtyInfoList member.
157     */
158    private void initFrameDirtyInfoList() {
159        this.frameDirtyInfoList.clear();
160        Integer frameNumber;
161        FrameDirtyInfo frameDirtyInfo;
162        for (int i=0; i<this.frameNumbers.size(); i++) {
163                frameNumber = (Integer)this.frameNumbers.get(i);
164                frameDirtyInfo = new FrameDirtyInfo(frameNumber.intValue(), false, false, false);
165                this.frameDirtyInfoList.add(frameDirtyInfo);
166        }
167    }
168    
169    /**
170     * Sets the frameDirtyInfoList member based on frame number
171     */
172    private void setFrameDirtyInfoList(int frameNumber, boolean dirtyImage, boolean dirtyGraphics, boolean dirtyColorTable) {
173        FrameDirtyInfo frameDirtyInfo;
174        for (int i=0; i<this.frameDirtyInfoList.size(); i++) {
175                frameDirtyInfo = (FrameDirtyInfo)frameDirtyInfoList.get(i);
176                if (frameDirtyInfo.getFrameNumber() == frameNumber) {
177                        frameDirtyInfo.setDirtyImage(dirtyImage);
178                        frameDirtyInfo.setDirtyGraphics(dirtyGraphics);
179                        frameDirtyInfo.setDirtyColorTable(dirtyColorTable);
180                        this.frameDirtyInfoList.set(i, frameDirtyInfo);
181                }
182        }
183    }
184    
185    /**
186     * Override the base class method that creates request properties
187     * and add in the appropriate frame component request parameters.
188     * @return  table of properties
189     */
190    protected Hashtable getRequestProperties() {
191        Hashtable props = super.getRequestProperties();
192        props.put(McIdasComponents.IMAGE, new Boolean(this.frameComponentInfo.getIsImage()));
193        props.put(McIdasComponents.GRAPHICS, new Boolean(this.frameComponentInfo.getIsGraphics()));
194        props.put(McIdasComponents.COLORTABLE, new Boolean(this.frameComponentInfo.getIsColorTable()));
195        props.put(McIdasComponents.ANNOTATION, new Boolean(this.frameComponentInfo.getIsAnnotation()));
196        props.put(McIdasComponents.FAKEDATETIME, new Boolean(this.frameComponentInfo.getFakeDateTime()));
197        props.put(McIdasComponents.DIRTYINFO, this.frameDirtyInfoList);
198        return props;
199    }
200
201    /**
202     * A helper method for constructing the ui.
203     * This fills up a list of {@link ControlWidget}
204     * (e.g., ColorTableWidget) and creates a gridded
205     * ui  with them.
206     *
207     * @return The ui for the widgets
208     */
209    protected JComponent doMakeWidgetComponent() {
210
211        JPanel framePanel = new JPanel();
212        try {
213                framePanel = GuiUtils.top(doMakeFramePanel());
214        } catch (Exception e) {
215                System.err.println("doMakeContents exception: " + e);
216        }
217        
218        JComponent settingsPanel = GuiUtils.top(super.doMakeWidgetComponent());
219        
220        JTabbedPane tabbedPane = new JTabbedPane();
221        tabbedPane.add("Frames", framePanel);
222        tabbedPane.add("Settings", settingsPanel);
223
224        return tabbedPane;
225 
226    }
227    
228    /**
229     * Get control widgets specific to this control.
230     *
231     * @param controlWidgets   list of control widgets from other places
232     *
233     * @throws RemoteException  Java RMI error
234     * @throws VisADException   VisAD Error
235     */
236    public void getControlWidgets(List controlWidgets)
237        throws VisADException, RemoteException {
238
239        super.getControlWidgets(controlWidgets);
240        
241        // Navigated checkbox
242        navigatedCbx = new JCheckBox("Display data in main 3D panel", false);
243        navigatedCbx.setToolTipText("Set to send navigated data to the main 3D display in addition to this 2D display");
244        navigatedCbx.addItemListener(new ItemListener() {
245            public void itemStateChanged(ItemEvent e) {
246                JCheckBox myself = (JCheckBox)e.getItemSelectable();
247                GuiUtils.enableTree(frameNavigatedContent, myself.isSelected());
248                updateVImage();
249            }
250         });
251        JPanel frameNavigatedCbx =
252                GuiUtils.hflow(Misc.newList(navigatedCbx), 2, 0);
253        controlWidgets.add(
254                        new WrapperWidget( this, GuiUtils.rLabel("Data:"), frameNavigatedCbx));
255        
256        // Navigated options
257        JPanel frameComponentsPanel =
258            GuiUtils.hflow(Misc.newList(doMakeImageBox(), doMakeGraphicsBox(), doMakeAnnotationBox()), 2, 0);
259        JPanel frameOrderPanel =
260                GuiUtils.hflow(Misc.newList(doMakeFakeDateTimeBox()), 2, 0);
261        JPanel frameProjectionPanel =
262                GuiUtils.hflow(Misc.newList(doMakeResetProjectionBox()), 2, 0);
263        frameNavigatedContent = 
264                GuiUtils.vbox(frameComponentsPanel, frameOrderPanel, frameProjectionPanel);
265        GuiUtils.enableTree(frameNavigatedContent, false);
266        controlWidgets.add(
267                        new WrapperWidget( this, GuiUtils.rLabel(""), frameNavigatedContent));
268
269    }
270    
271    /**
272     * Get frame control widgets specific to this control.
273     *
274     * @throws RemoteException  Java RMI error
275     * @throws VisADException   VisAD Error
276     */
277    private JPanel doMakeFramePanel() 
278        throws VisADException, RemoteException {
279        frameSize = new Dimension(640, 480);
280        
281        JPanel framePanel = new JPanel(new GridBagLayout());
282        GridBagConstraints c = new GridBagConstraints();
283        c.gridwidth = GridBagConstraints.REMAINDER;
284        
285        frmI = new Integer(0);
286        ControlContext controlContext = getControlContext();
287        List dss = ((IntegratedDataViewer)controlContext).getDataSources();
288        for (int i=0; i<dss.size(); i++) {
289                DataSourceImpl ds = (DataSourceImpl)dss.get(i);
290                if (ds instanceof McIdasXDataSource) {
291                        frameNumbers.clear();
292                        ds.setProperty(DataSource.PROP_AUTOCREATEDISPLAY, false);
293                        mcidasxDS = (McIdasXDataSource)ds;
294                        DataContext dataContext = mcidasxDS.getDataContext();
295                        ColorTableManager colorTableManager = 
296                                ((IntegratedDataViewer)dataContext).getColorTableManager();
297                        ColorTable ct = colorTableManager.getColorTable("McIDAS-X");
298                        setColorTable(ct);
299                        this.mcidasxInfo = mcidasxDS.getMcIdasXInfo();
300                        this.dc = getDataChoice();
301                        String choiceStr = this.dc.toString();
302                        if (choiceStr.equals("Frame Sequence")) {
303                                frameNumbers = mcidasxDS.getFrameNumbers();
304                        } else {
305                                StringTokenizer tok = new StringTokenizer(choiceStr);
306                                String str = tok.nextToken();
307                                if (str.equals("Frame")) {
308                                        frmI = new Integer(tok.nextToken());
309                                        frameNumbers.add(frmI);
310                                } else {
311                                        frmI = new Integer(1);
312                                        frameNumbers.add(frmI);
313                                }
314                        }
315                        break;
316                }
317        }
318        initFrameDirtyInfoList();
319        
320        // McIDAS-X frame display
321        frameDisplay = new McIdasFrameDisplay(frameNumbers, frameSize);
322        for (int i=0; i<frameNumbers.size(); i++) {
323                updateXImage((Integer)frameNumbers.get(i));
324                if (i==0) showXImage((Integer)frameNumbers.get(i));
325        }
326        framePanel.add(GuiUtils.hflow(Misc.newList(frameDisplay)), c);
327        
328        // McIDAS-X text stuff
329        outputPane = doMakeOutputText();
330        inputText = doMakeCommandLine();
331        JPanel commandLinePanel =
332                GuiUtils.vbox(outputPane, doMakeSpacer(), inputText);
333        commandLinePanel.setBorder(BorderFactory.createLineBorder(Color.black));
334        framePanel.add(GuiUtils.hflow(Misc.newList(commandLinePanel)), c);
335        
336        // McIDAS-X commands that are running
337        runningThreads = GuiUtils.lLabel("Running: " + this.threadCount);
338        framePanel.add(GuiUtils.hflow(Misc.newList(runningThreads)), c);
339       
340        // Create a sensible title and tell McIDAS-X to stop looping and go to the first frame
341        String title = "";
342        if (frameNumbers.size() == 1) {
343                title = "McIDAS-X Frame " + (Integer)frameNumbers.get(0);
344        }
345        else {
346                Integer first = (Integer)frameNumbers.get(0);
347                Integer last = (Integer)frameNumbers.get(frameNumbers.size() - 1);
348                if (last - first == frameNumbers.size() - 1) {
349                        title = "McIDAS-X Frames " + first + "-" + last;
350                }
351                else {
352                        title = "McIDAS-X Frames " + (Integer)frameNumbers.get(0);
353                        for (int i=1; i<frameNumbers.size(); i++) {
354                                title += ", " + (Integer)frameNumbers.get(i);
355                        }
356                }
357        }
358        sendCommandLine("TERM L OFF; SF " + (Integer)frameNumbers.get(0), false);
359           
360        setNameFromUser(title);
361
362        // Give inputText the focus whenever anything is clicked on...
363        framePanel.addMouseListener(new MouseAdapter() {
364                public void mouseClicked(MouseEvent me) {
365                        if (me.getButton() == me.BUTTON1) {
366                                inputText.requestFocus();
367                        }
368                }
369        });
370        
371        return framePanel;
372    }
373
374    /**
375     * Make the frame component check boxes.
376     * @return Check box for Images
377     */
378        protected Component doMakeImageBox() {
379        JCheckBox newBox = new JCheckBox("Image", frameComponentInfo.getIsImage());
380        newBox.setToolTipText("Set to import image data");
381        newBox.addItemListener(new ItemListener() {
382                public void itemStateChanged(ItemEvent e) {
383                        JCheckBox myself = (JCheckBox)e.getItemSelectable();
384                        frameComponentInfo.setIsImage(myself.isSelected());
385                        updateVImage();
386                }
387        });
388        return newBox;
389        }
390
391    /**
392     * Make the frame component check boxes.
393     * @return Check box for Graphics
394     */
395    protected Component doMakeGraphicsBox() {
396        JCheckBox newBox = new JCheckBox("Graphics", frameComponentInfo.getIsGraphics());
397        newBox.setToolTipText("Set to import graphics data");
398        newBox.addItemListener(new ItemListener() {
399                public void itemStateChanged(ItemEvent e) {
400                        JCheckBox myself = (JCheckBox)e.getItemSelectable();
401                        frameComponentInfo.setIsGraphics(myself.isSelected());
402                        updateVImage();
403                }
404        });
405        return newBox;
406        }
407
408    /**
409     * Make the frame component check boxes.
410     * @return Check box for Color table
411     */
412    protected Component doMakeColorTableBox() {
413        JCheckBox newBox = new JCheckBox("Color table", frameComponentInfo.getIsColorTable());
414        newBox.setToolTipText("Set to import color table data");
415        newBox.addItemListener(new ItemListener() {
416                public void itemStateChanged(ItemEvent e) {
417                        JCheckBox myself = (JCheckBox)e.getItemSelectable();
418                        frameComponentInfo.setIsColorTable(myself.isSelected());
419                        updateVImage();
420                }
421        });
422        return newBox;
423        }
424    
425    /**
426     * Make the frame component check boxes.
427     * @return Check box for Annotation line
428     */
429    protected Component doMakeAnnotationBox() {
430        JCheckBox newBox = new JCheckBox("Annotation line", frameComponentInfo.getIsAnnotation());
431        newBox.setToolTipText("Set to import annotation line");
432        newBox.addItemListener(new ItemListener() {
433                public void itemStateChanged(ItemEvent e) {
434                        JCheckBox myself = (JCheckBox)e.getItemSelectable();
435                        frameComponentInfo.setIsAnnotation(myself.isSelected());
436                        updateVImage();
437                }
438        });
439        return newBox;
440        }
441    
442    /**
443     * Make the frame behavior check boxes.
444     * @return Check box for Fake date/time
445     */
446    protected Component doMakeFakeDateTimeBox() {
447        JCheckBox newBox =
448                new JCheckBox("Use McIDAS-X frame order to override data time with frame number",
449                        frameComponentInfo.getFakeDateTime());
450        newBox.setToolTipText("Set to preserve frame order");
451        newBox.addItemListener(new ItemListener() {
452                public void itemStateChanged(ItemEvent e) {
453                        JCheckBox myself = (JCheckBox)e.getItemSelectable();
454                        frameComponentInfo.setFakeDateTime(myself.isSelected());
455                        updateVImage();
456                }
457        });
458        return newBox;
459        }
460    
461    /**
462     * Make the frame behavior check boxes.
463     * @return Check box for Projection reset
464     */
465    protected Component doMakeResetProjectionBox() {
466        JCheckBox newBox =
467                new JCheckBox("Use McIDAS-X data projection",
468                        frameComponentInfo.getResetProjection());
469        newBox.setToolTipText("Set to reset projection when data is refreshed");
470        newBox.addItemListener(new ItemListener() {
471                public void itemStateChanged(ItemEvent e) {
472                        JCheckBox myself = (JCheckBox)e.getItemSelectable();
473                        frameComponentInfo.setResetProjection(myself.isSelected());
474                        updateVImage();
475                }
476        });
477        return newBox;
478        }
479    
480    private void resetCommandHistory() {
481        commandHistory = new ArrayList();
482        resetCommandHistoryIdx();
483    }
484    
485    private void resetCommandHistoryIdx() {
486        commandHistoryIdx = -1;
487    }
488    
489    protected JTextField doMakeCommandLine() {
490                final JTextField commandLine = new JTextField(0);
491                commandLine.setFont(outputFont);
492                commandLine.setBackground(Color.black);
493                commandLine.setForeground(Color.cyan);
494                commandLine.setCaretColor(Color.cyan);
495                
496                FontMetrics metrics = commandLine.getFontMetrics(outputFont);
497        Dimension d = new Dimension(frameSize.width, metrics.getHeight());
498        commandLine.setSize(d);
499        commandLine.setPreferredSize(d);
500        commandLine.setMinimumSize(d);
501        commandLine.setMaximumSize(d);
502        
503        resetCommandHistory();
504                
505                commandLine.addActionListener(new ActionListener() {
506                        public void actionPerformed(ActionEvent ae) {
507                                String line = commandLine.getText().trim();
508                                if (line.equals("")) return;
509                                commandLine.setText("");                        
510                                sendCommandLineThread(line, true);
511                                
512                                // Add it to the head of commandHistory list
513                                commandHistory.add(0, line);
514                                resetCommandHistoryIdx();
515                        }
516                });
517                commandLine.addKeyListener(new KeyAdapter() {
518                        public void keyTyped(KeyEvent ke) {
519                                char keyChar = ke.getKeyChar();
520                                if (commandLine.getText().trim().equals("") && commandHistory.size() > 0) {
521                                        commandHistoryMode = true;
522                                }
523                                if (commandHistoryMode) {
524                                        if (keyChar == '&') {
525                                                commandHistoryIdx = Math.min(commandHistoryIdx+1,commandHistory.size()-1);
526                                                if (commandHistoryIdx < 0) {
527                                                        resetCommandHistoryIdx();
528                                                        commandLine.setText("");
529                                                }
530                                                else {
531                                                        commandLine.setText((String)commandHistory.get(commandHistoryIdx));
532                                                }
533                                                ke.consume();
534                                        }
535                                        else if (keyChar == '^') {
536                                                commandHistoryIdx--;
537                                                if (commandHistoryIdx < 0) {
538                                                        resetCommandHistoryIdx();
539                                                        commandLine.setText("");
540                                                }
541                                                else {
542                                                        commandLine.setText((String)commandHistory.get(commandHistoryIdx));
543                                                }
544                                                ke.consume();
545                                        }
546                                        else {
547                                                commandHistoryMode = false;
548                                        }
549                                }
550                if (Character.isLowerCase(keyChar))
551                        keyChar = Character.toUpperCase(keyChar);
552                else
553                        keyChar = Character.toLowerCase(keyChar);
554                ke.setKeyChar(keyChar);
555            }
556        });
557                commandLine.setBorder(new EmptyBorder(0,0,0,0));
558                return commandLine;
559    }
560    
561    private Component doMakeSpacer() {
562        JPanel spacer = new JPanel();
563        Color backgroundColor = new Color(0, 128, 128);
564        spacer.setBackground(backgroundColor);
565                FontMetrics metrics = inputText.getFontMetrics(outputFont);
566        Dimension d = new Dimension(frameSize.width, metrics.getHeight());
567        spacer.setSize(d);
568        spacer.setPreferredSize(d);
569        spacer.setMinimumSize(d);
570        spacer.setMaximumSize(d);
571        spacer.setBorder(new EmptyBorder(0,0,0,0));
572        return spacer;
573    }
574    
575    protected JScrollPane doMakeOutputText() {          
576        JTextPane outputPane = new JTextPane() {
577                public void setSize(Dimension d) {
578                                if (d.width < getParent().getSize().width)
579                                        d.width = getParent().getSize().width;
580                                super.setSize(d);
581                        }
582                        public boolean getScrollableTracksViewportWidth() {
583                                return false;
584                        }
585        };
586        outputPane.setFont(outputFont);
587        outputPane.setEditable(false);
588        outputPane.setBackground(Color.black);
589        outputPane.setForeground(Color.lightGray);
590        JScrollPane outputScrollPane = new JScrollPane(outputPane,
591                        ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
592                        ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
593        
594                FontMetrics metrics = outputPane.getFontMetrics(outputFont);
595        Dimension d = new Dimension(frameSize.width, metrics.getHeight() * 8);
596        outputScrollPane.setSize(d);
597        outputScrollPane.setPreferredSize(d);
598        outputScrollPane.setMinimumSize(d);
599        outputScrollPane.setMaximumSize(d);
600        
601        outputText = (StyledDocument)outputPane.getDocument();
602        outputScrollPane.setBorder(new EmptyBorder(0,0,0,0));
603        
604        return outputScrollPane;
605    }
606
607    /**
608     * Send the given commandline to McIDAS-X over the bridge
609     * @param line
610     * @param showprocess
611     */
612        private void sendCommandLine(String line, boolean showprocess) {
613                
614        // The user might have moved to another frame...
615        // Ask the image display which frame we are on
616                int frameCur = 1;
617                if (frameDisplay != null)
618                        frameCur = frameDisplay.getFrameNumber();
619                
620        line = line.trim();
621        if (line.length() < 1) return;
622        String encodedLine = line;
623        try {
624                encodedLine = URLEncoder.encode(line,"UTF-8");
625        } catch (Exception e) {
626                System.out.println("sendCommandLine URLEncoder exception: " + e);
627        }
628        
629        DataInputStream inputStream = mcidasxInfo.getCommandInputStream(encodedLine, frameCur);
630        if (!showprocess) {
631            try { inputStream.close(); }
632            catch (Exception e) {}
633                return;
634        }
635//        appendTextLineItalics(line);
636        appendTextLineCommand(line);
637        try {
638                BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
639                String responseType = null;
640                String ULine = null;
641                StringTokenizer tok;
642                boolean inList = false;
643                boolean doUpdate = false;
644                boolean dirtyImage, dirtyGraphics, dirtyColorTable;
645                // Burn the session key header line
646                String lineOut = br.readLine();
647                lineOut = br.readLine();
648                while (lineOut != null) {
649//                      System.out.println("sendCommandLine processing: " + lineOut);
650                        tok = new StringTokenizer(lineOut, " ");
651                        responseType = tok.nextToken();
652                        if (responseType.equals("U")) {
653                        Integer frameInt = (Integer)Integer.parseInt(tok.nextToken());
654                        inList = false;
655                        dirtyImage = dirtyGraphics = dirtyColorTable = false;
656                        // Don't pay attention to anything outside of our currently loaded frame list
657                                for (int i=0; i<frameNumbers.size(); i++) {
658                                        if (frameInt.compareTo((Integer)frameNumbers.get(i)) == 0) inList = true;
659                                }
660                                if (inList) {
661                                        ULine = tok.nextToken();
662//                                      System.out.println("  Frame " + frameInt + " status line: " + ULine);
663                                        if (Integer.parseInt(ULine.substring(1,2)) != 0) dirtyImage = true;
664                                        if (Integer.parseInt(ULine.substring(3,4)) != 0) dirtyGraphics = true;
665                                        if (Integer.parseInt(ULine.substring(5,6)) != 0) dirtyColorTable = true;
666                                        if (dirtyImage || dirtyGraphics || dirtyColorTable) {
667                                                doUpdate = true;
668                                                updateXImage(frameInt);
669                                        }
670                                        setFrameDirtyInfoList(frameInt, dirtyImage, dirtyGraphics, dirtyColorTable);
671                                }
672                        } else if (responseType.equals("C")) {
673                                appendTextLineCommand(lineOut.substring(6));
674                } else if (responseType.equals("T")) {
675//                      appendTextLine("   " + lineOut.substring(6));
676                        appendTextLineNormal(lineOut.substring(6));
677                        
678                } else if (responseType.equals("M") ||
679                           responseType.equals("S")) {
680//                      appendTextLine(" * " + lineOut.substring(6));
681                        appendTextLineError(lineOut.substring(6));
682                        
683                } else if (responseType.equals("R")) {
684//                      appendTextLine(" ! " + lineOut.substring(6));
685                        appendTextLineError(lineOut.substring(6));
686                } else if (responseType.equals("V")) {
687//                      System.out.println("Viewing frame status line: " + lineOut);
688                        frameCur = Integer.parseInt(tok.nextToken());
689                        } else if (responseType.equals("H") ||
690                                       responseType.equals("K")) {
691                                /* Don't do anything with these response types */
692                        } else {
693                                /* Catch any unparsed line... */
694                                System.err.println("Could not parse bridge response: " + lineOut);
695                        }
696                        lineOut = br.readLine();
697                }
698                showXImage(frameCur);
699                        if (doUpdate) {
700                                updateVImage();
701                        }
702                        
703            } catch (Exception e) {
704                System.out.println("sendCommandLine exception: " + e);
705            try { inputStream.close(); }
706            catch (Exception ee) {}
707            }
708
709        }
710
711/*
712        private void appendTextLine(String line) {
713                outputText.append(line + "\n");
714                outputText.setCaretPosition(outputText.getDocument().getLength());
715        }
716*/
717        
718        private void appendTextLineNormal(String line) {
719        Style style = outputText.addStyle("Normal", null);
720        StyleConstants.setForeground(style, Color.lightGray);
721        try {
722                outputText.insertString(outputText.getLength(), line + "\n", style);
723        } catch (BadLocationException e) { }
724        scrollTextLineToBottom();
725        }
726        
727        private void appendTextLineCommand(String line) {
728        Style style = outputText.addStyle("Command", null);
729        StyleConstants.setForeground(style, Color.green);
730        try {
731                outputText.insertString(outputText.getLength(), line + "\n", style);
732        } catch (BadLocationException e) { }
733        scrollTextLineToBottom();
734        }
735        
736        private void appendTextLineError(String line) {
737        Style style = outputText.addStyle("Error", null);
738        StyleConstants.setForeground(style, Color.yellow);
739        try {
740                outputText.insertString(outputText.getLength(), line + "\n", style);
741        } catch (BadLocationException e) { }
742        scrollTextLineToBottom();
743        }
744
745        private void scrollTextLineToBottom() {
746                SwingUtilities.invokeLater(new Runnable() {
747                        public void run() {
748                                JScrollBar vBar = outputPane.getVerticalScrollBar();
749                                vBar.setValue(vBar.getMaximum());
750                        }
751                });
752        }
753        
754        private void updateXImage(int inFrame) {
755                if (mcidasxDS == null || frameDisplay == null) return;
756                try {
757                        McIdasFrame frm = mcidasxDS.getFrame(inFrame);
758                        Image imageGIF = frm.getGIF();
759                        frameDisplay.setFrameImage(inFrame, imageGIF);
760                } catch (Exception e) {
761                        System.out.println("updateXImage exception: " + e);
762                }
763        }
764        
765        private void showXImage(int inFrame) {
766                if (frameDisplay == null) return;
767                try {
768                        frameDisplay.showFrameNumber(inFrame);
769                } catch (Exception e) {
770                        System.out.println("showXImage exception: " + e);
771                }
772        }
773        
774    private void updateVImage() {
775        try {
776                getRequestProperties();
777            resetData();
778        } catch (Exception e) {
779            System.out.println("updateVImage exception: " + e);
780        }
781    }
782
783    public boolean init(DataChoice choice)
784                        throws VisADException, RemoteException {
785        setShowProgressBar(false);
786        boolean ret = super.init(choice, false);
787        return ret;
788    }
789    
790    /**
791     * This gets called when the control has received notification of a
792     * dataChange event.
793     * 
794     * @throws RemoteException   Java RMI problem
795     * @throws VisADException    VisAD problem
796     */
797    protected void resetData() throws VisADException, RemoteException {
798        // Do not attempt to load any data unless the checkbox is set...
799        if (!navigatedCbx.isSelected()) return;
800        
801        super.resetData();
802
803        if (frameComponentInfo.getResetProjection()) {
804                MapProjection mp = getDataProjection();
805                if (mp != null) {
806                        MapViewManager mvm = getMapViewManager();
807                        mvm.setMapProjection(mp, false); 
808                }
809        }
810    }
811    
812    /**
813     * Try my hand at creating a thread
814     */
815    private class McIdasCommandLine implements Runnable {
816        private String line;
817        private boolean showprocess;
818        public McIdasCommandLine() {
819                this.line = "";
820                this.showprocess = true;
821        }
822        public McIdasCommandLine(String line, boolean showprocess) {
823                this.line = line;
824                this.showprocess = showprocess;
825        }
826        public void run() {
827                notifyThreadStart();
828                sendCommandLine(this.line, this.showprocess);
829                notifyThreadStop();
830        }
831    }
832    
833    /**
834     * Threaded sendCommandLine
835     * @param line
836     * @param showprocess
837     */
838    private void sendCommandLineThread(String line, boolean showprocess) {
839        McIdasCommandLine mcCmdLine = new McIdasCommandLine(line, showprocess);
840        Thread t = new Thread(mcCmdLine);
841        t.start();
842    }
843    
844    private void notifyThreadStart() {
845        this.threadCount++;
846        notifyThreadCount();
847    }
848    private void notifyThreadStop() {
849        this.threadCount--;
850        notifyThreadCount();
851    }
852    private void notifyThreadCount() {
853        runningThreads.setText("Running: " + this.threadCount);
854    }
855
856}