001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2023
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 http://www.gnu.org/licenses.
027 */
028
029package edu.wisc.ssec.mcidasv.chooser;
030
031import static javax.swing.GroupLayout.DEFAULT_SIZE;
032import static javax.swing.GroupLayout.Alignment.BASELINE;
033import static javax.swing.GroupLayout.Alignment.LEADING;
034import static javax.swing.GroupLayout.Alignment.TRAILING;
035import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
036import static javax.swing.LayoutStyle.ComponentPlacement.UNRELATED;
037
038import java.awt.event.ActionEvent;
039import java.awt.event.ActionListener;
040import java.awt.event.KeyAdapter;
041import java.awt.event.KeyEvent;
042import java.awt.event.KeyListener;
043import java.util.Hashtable;
044import java.util.List;
045
046import javax.swing.GroupLayout;
047import javax.swing.JButton;
048import javax.swing.JComboBox;
049import javax.swing.JComponent;
050import javax.swing.JLabel;
051import javax.swing.JPanel;
052import javax.swing.JRadioButton;
053import javax.swing.JScrollPane;
054import javax.swing.JTextArea;
055import javax.swing.JTextField;
056import javax.swing.JToggleButton;
057
058import org.w3c.dom.Element;
059
060import edu.wisc.ssec.mcidasv.Constants;
061import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
062import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Position;
063import edu.wisc.ssec.mcidasv.util.McVGuiUtils.TextColor;
064import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Width;
065
066import ucar.unidata.data.DataManager;
067import ucar.unidata.idv.chooser.IdvChooserManager;
068import ucar.unidata.util.GuiUtils;
069import ucar.unidata.util.LayoutUtil;
070import ucar.unidata.util.PreferenceList;
071import ucar.unidata.util.StringUtil;
072
073/**
074 * Allows the user to select a url as a data source.
075 */
076public class UrlChooser extends ucar.unidata.idv.chooser.UrlChooser implements Constants {
077
078    /** Property ID for the contents of {@link #textArea}. */
079    public static final String PROP_MULTI_URLS =
080        "mcidasv.chooser.url.multiurlcontents";
081
082    /**
083     * Property ID for the {@literal "URL type"} selection.
084     * Values will be either {@link #MULTIPLE_URL_VALUE} or
085     * {@link #SINGLE_URL_VALUE}.
086     */
087    public static final String PROP_URL_TYPE = "mcidasv.chooser.url.urltype";
088
089    /** Multiple URL selection value for {@link #PROP_URL_TYPE}. */
090    public static final String MULTIPLE_URL_VALUE = "multiple";
091
092    /** Single URL selection value for {@link #PROP_URL_TYPE}. */
093    public static final String SINGLE_URL_VALUE = "single";
094
095    /** Manages the pull down list of URLs. */
096    private PreferenceList prefList;
097
098    /** List of URLs. */
099    private JComboBox box;
100
101    private JTextField boxEditor;
102
103    /** Text area for multi-line URLs. */
104    private JTextArea textArea;
105
106    /** Text scroller. */
107    private JScrollPane textScroller;
108
109    /** Holds the combo box. */
110    private JPanel urlPanel;
111
112    /** Holds the text area. */
113    private JPanel textPanel;
114
115    /** Are we showing {@link #textPanel}? */
116    private boolean showBox = true;
117
118    /**
119     * Panel that allows switching between {@link #urlPanel} and
120     * {@link #textPanel}.
121     */
122    private GuiUtils.CardLayoutPanel cardLayoutPanel;
123
124    private JLabel statusLabel = new JLabel("Status");
125
126    /**
127     * Create the {@code UrlChooser}.
128     *
129     * @param mgr {@code IdvChooserManager}.
130     * @param root XML root that defines this chooser.
131     */
132    public UrlChooser(IdvChooserManager mgr, Element root) {
133        super(mgr, root);
134
135        String loadCommand = getLoadCommandName();
136        loadButton =
137            McVGuiUtils.makeImageTextButton(ICON_ACCEPT_SMALL, loadCommand);
138        loadButton.setActionCommand(getLoadCommandName());
139        loadButton.addActionListener(this);
140
141    }
142
143    /**
144     * Show {@link #urlPanel} or {@link #textPanel}, depending on the value of
145     * {@link #showBox}.
146     */
147    @Override public void switchFields() {
148        if (showBox) {
149            cardLayoutPanel.show(urlPanel);
150        } else {
151            cardLayoutPanel.show(textPanel);
152        }
153        updateStatus();
154    }
155
156    /**
157     * Disable/enable any components that depend on the server.
158     * Try to update the status label with what we know here.
159     */
160    @Override protected void updateStatus() {
161        if ((boxEditor != null) && (textArea != null)) {
162            if (showBox) {
163                setHaveData(!boxEditor.getText().trim().isEmpty());
164            } else {
165                setHaveData(!textArea.getText().trim().isEmpty());
166            }
167            super.updateStatus();
168            if (!getHaveData()) {
169                if (showBox) {
170                    setStatus("Enter a URL.");
171                } else {
172                    setStatus("Enter one or more URLs.");
173                }
174            }
175        }
176    }
177
178    /**
179     * Handle the action event from the GUI.
180     */
181    @Override public void doLoadInThread() {
182        loadURL();
183    }
184
185    /**
186     * Wrapper around {@link #loadURLInner()}, showing the wait cursor.
187     */
188    private void loadURL() {
189        showWaitCursor();
190        loadURLInner();
191        showNormalCursor();
192    }
193
194    /**
195     * Load the URL.
196     */
197    private void loadURLInner() {
198
199        String url = "";
200        String dataSourceId = getDataSourceId();
201        if (showBox) {
202            Object selectedItem = box.getSelectedItem();
203            if (selectedItem != null) {
204                url = selectedItem.toString().trim();
205            }
206            if (url.isEmpty() && (dataSourceId == null)) {
207                userMessage("Please specify a URL.");
208                return;
209            }
210        }
211
212        Hashtable props = new Hashtable();
213        if (dataSourceId != null) {
214            props.put(DataManager.DATATYPE_ID, dataSourceId);
215        }
216
217        if (showBox) {
218            if (getIdv().handleAction(url, props)) {
219                closeChooser();
220                prefList.saveState(box);
221            }
222        } else {
223            List<String> urls =
224                StringUtil.split(textArea.getText(), "\n", true, true);
225
226            if (!urls.isEmpty() && makeDataSource(urls, dataSourceId, props)) {
227                closeChooser();
228                getIdv().getStore().put(PROP_MULTI_URLS, textArea.getText());
229            }
230        }
231    }
232
233    /**
234     * Make the contents.
235     *
236     * @return  the contents
237     */
238    protected JPanel doMakeInnerPanel() {
239
240        String urlType =
241            getIdv().getStore().get(PROP_URL_TYPE, SINGLE_URL_VALUE);
242        if (MULTIPLE_URL_VALUE.equals(urlType)) {
243            showBox = false;
244        }
245
246        final JToggleButton singleBtn = new JRadioButton("Single", showBox);
247        singleBtn.addActionListener(e -> {
248            getIdv().getStore().put(PROP_URL_TYPE, SINGLE_URL_VALUE);
249            showBox = true;
250            switchFields();
251        });
252
253        final JToggleButton multipleBtn =
254            new JRadioButton("Multiple", !showBox);
255        multipleBtn.addActionListener(e -> {
256            getIdv().getStore().put(PROP_URL_TYPE, MULTIPLE_URL_VALUE);
257            showBox = false;
258            switchFields();
259        });
260        GuiUtils.buttonGroup(singleBtn, multipleBtn);
261        JPanel radioPanel = LayoutUtil.hbox(singleBtn, multipleBtn);
262        
263        prefList = getPreferenceList(PREF_URLLIST);
264        box = prefList.createComboBox(CMD_LOAD, this);
265        boxEditor = (JTextField)box.getEditor().getEditorComponent();
266        boxEditor.addKeyListener(new KeyAdapter() {
267            @Override public void keyReleased(KeyEvent e) {
268                updateStatus();
269            }
270        });
271        
272        textArea = new JTextArea(5, 30);
273        textScroller = new JScrollPane(textArea);
274        textArea.addKeyListener(new KeyAdapter() {
275            @Override public void keyReleased(KeyEvent e) {
276                updateStatus();
277            }
278        });
279        
280        urlPanel = LayoutUtil.top(box);
281        textPanel = LayoutUtil.top(textScroller);
282        
283        cardLayoutPanel = new GuiUtils.CardLayoutPanel();
284        cardLayoutPanel.addCard(urlPanel);
285        cardLayoutPanel.addCard(textPanel);
286        
287        JPanel showPanel =
288            McVGuiUtils.topBottom(radioPanel, cardLayoutPanel, null);
289        
290        setHaveData(false);
291        updateStatus();
292        if (!showBox) {
293            textArea.setText(getIdv().getStore().get(PROP_MULTI_URLS, ""));
294            switchFields();
295        }
296        return McVGuiUtils.makeLabeledComponent("URL:", showPanel);
297    }
298
299    @Override public void setStatus(String statusString, String foo) {
300        if (statusString == null) {
301            statusString = "";
302        }
303        statusLabel.setText(statusString);
304    }
305
306    /**
307     * Create a more McIDAS-V-like GUI layout
308     */
309    protected JComponent doMakeContents() {
310        JComponent typeComponent = getDataSourcesComponent();
311        if (typeComponent == null) {
312            typeComponent = new JLabel("No data type specified");
313        }
314
315        JPanel innerPanel = doMakeInnerPanel();
316        
317        // Start building the whole thing here
318        JPanel outerPanel = new JPanel();
319
320        JLabel typeLabel = McVGuiUtils.makeLabelRight("Data Type:");
321                
322        JLabel statusLabelLabel = McVGuiUtils.makeLabelRight("");
323        
324        statusLabel.setText("Status");
325        McVGuiUtils.setLabelPosition(statusLabel, Position.RIGHT);
326        McVGuiUtils.setComponentColor(statusLabel, TextColor.STATUS);
327        
328        JButton helpButton =
329            McVGuiUtils.makeImageButton(ICON_HELP, "Show help");
330        helpButton.setActionCommand(GuiUtils.CMD_HELP);
331        helpButton.addActionListener(this);
332        
333        JButton refreshButton =
334            McVGuiUtils.makeImageButton(ICON_REFRESH, "Refresh");
335        refreshButton.setActionCommand(GuiUtils.CMD_UPDATE);
336        refreshButton.addActionListener(this);
337        
338        McVGuiUtils.setComponentWidth(loadButton, Width.DOUBLE);
339
340        GroupLayout layout = new GroupLayout(outerPanel);
341        outerPanel.setLayout(layout);
342        layout.setHorizontalGroup(
343            layout.createParallelGroup(LEADING)
344            .addGroup(TRAILING, layout.createSequentialGroup()
345                .addGroup(layout.createParallelGroup(TRAILING)
346                    .addGroup(layout.createSequentialGroup()
347                        .addContainerGap()
348                        .addComponent(helpButton)
349                        .addGap(GAP_RELATED)
350                        .addComponent(refreshButton)
351                        .addPreferredGap(RELATED)
352                        .addComponent(loadButton))
353                        .addGroup(LEADING, layout.createSequentialGroup()
354                        .addContainerGap()
355                        .addGroup(layout.createParallelGroup(LEADING)
356                            .addComponent(innerPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
357                            .addGroup(layout.createSequentialGroup()
358                                .addComponent(typeLabel)
359                                .addGap(GAP_RELATED)
360                                .addComponent(typeComponent))
361                            .addGroup(layout.createSequentialGroup()
362                                .addComponent(statusLabelLabel)
363                                .addGap(GAP_RELATED)
364                                .addComponent(statusLabel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)))))
365                .addContainerGap())
366        );
367        layout.setVerticalGroup(
368            layout.createParallelGroup(LEADING)
369            .addGroup(layout.createSequentialGroup()
370                .addContainerGap()
371                .addGroup(layout.createParallelGroup(BASELINE)
372                    .addComponent(typeLabel)
373                    .addComponent(typeComponent))
374                .addPreferredGap(UNRELATED)
375                .addComponent(innerPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
376                .addPreferredGap(UNRELATED)
377                .addGroup(layout.createParallelGroup(BASELINE)
378                    .addComponent(statusLabelLabel)
379                    .addComponent(statusLabel))
380                .addPreferredGap(UNRELATED)
381                .addGroup(layout.createParallelGroup(BASELINE)
382                    .addComponent(loadButton)
383                    .addComponent(refreshButton)
384                    .addComponent(helpButton))
385                .addContainerGap())
386        );
387        return outerPanel;
388    }
389}
390