001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2025 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.ui; 030 031import static java.awt.Color.GRAY; 032 033import static javax.swing.BorderFactory.createBevelBorder; 034import static javax.swing.GroupLayout.Alignment.LEADING; 035import static javax.swing.border.BevelBorder.RAISED; 036 037import static ucar.unidata.util.GuiUtils.getImageIcon; 038import static ucar.unidata.util.LayoutUtil.inset; 039import static ucar.unidata.util.LayoutUtil.topCenter; 040 041import java.awt.Cursor; 042import java.awt.Font; 043import java.awt.BorderLayout; 044import java.awt.event.KeyAdapter; 045import java.awt.event.KeyEvent; 046import java.awt.event.MouseAdapter; 047import java.awt.event.MouseEvent; 048 049import java.net.MalformedURLException; 050import java.net.URL; 051 052import java.util.Objects; 053import java.util.concurrent.atomic.AtomicBoolean; 054 055import javax.swing.GroupLayout; 056import javax.swing.JEditorPane; 057import javax.swing.JFrame; 058import javax.swing.JLabel; 059import javax.swing.JPanel; 060import javax.swing.JScrollPane; 061import javax.swing.JTabbedPane; 062import javax.swing.JTextArea; 063import javax.swing.JTextField; 064import javax.swing.SwingUtilities; 065import javax.swing.WindowConstants; 066import javax.swing.event.ChangeEvent; 067import javax.swing.event.ChangeListener; 068import javax.swing.event.HyperlinkEvent; 069import javax.swing.text.DefaultCaret; 070 071import org.slf4j.Logger; 072import org.slf4j.LoggerFactory; 073 074import ucar.unidata.ui.TextSearcher; 075import ucar.unidata.util.GuiUtils; 076import ucar.unidata.util.Misc; 077 078import edu.wisc.ssec.mcidasv.Constants; 079import edu.wisc.ssec.mcidasv.McIDASV; 080import edu.wisc.ssec.mcidasv.StateManager; 081import edu.wisc.ssec.mcidasv.util.SystemState; 082 083/** 084 * Class that represents the {@literal "Help>About McIDAS-V"} window. 085 */ 086class AboutFrame extends JFrame implements ChangeListener { 087 088 /** Logging object. */ 089 private static final Logger logger = 090 LoggerFactory.getLogger(AboutFrame.class); 091 092 /** 093 * Initial message in text area within {@literal "System Information"} tab. 094 */ 095 private static final String PLEASE_WAIT = 096 "Please wait, collecting system information..."; 097 098 /** Text used as the title for this window. */ 099 private static final String WINDOW_TITLE = "About McIDAS-V"; 100 101 /** Name of the first tab. */ 102 private static final String MCV_TAB_TITLE = "McIDAS-V"; 103 104 /** Name of the second tab. */ 105 private static final String SYS_TAB_TITLE = "System Information"; 106 107 /** Reference to the main McIDAS-V object. */ 108 private final McIDASV mcv; 109 110 /** 111 * Text area within the {@literal "System Information"} tab. 112 * Value may be {@code null}. 113 */ 114 private JTextArea sysTextArea; 115 116 /** Whether the system information has been collected. */ 117 private final AtomicBoolean hasSysInfo; 118 119 /** the text searching widget */ 120 private TextSearcher textSearcher; 121 122 /** 123 * Creates new form AboutFrame 124 * 125 * @param mcv McIDAS-V object. Cannot be {@code null}. 126 * 127 * @throws NullPointerException if {@code mcv} is {@code null}. 128 */ 129 AboutFrame(final McIDASV mcv) { 130 Objects.requireNonNull(mcv,"mcv reference cannot be null"); 131 this.mcv = mcv; 132 this.hasSysInfo = new AtomicBoolean(false); 133 initComponents(); 134 } 135 136 /** 137 * Convenience method for calling {@link SystemState#getStateAsString(McIDASV, boolean)}. 138 * 139 * @return <i>All</i> of the relevant McIDAS-V system properties, stuffed into a single {@code String}. 140 */ 141 private String getSystemInformation() { 142 return SystemState.getStateAsString(mcv, true); 143 } 144 145 /** 146 * Called by the constructor to initialize the {@literal "About"} window. 147 */ 148 private void initComponents() { 149 150 JTabbedPane tabbedPanel = new JTabbedPane(); 151 JPanel mcvTab = new JPanel(); 152 JPanel mcvPanel = buildAboutMcv(); 153 JPanel sysTab = new JPanel(); 154 JScrollPane sysScrollPane = new JScrollPane(); 155 sysTextArea = new JTextArea(); 156 157 textSearcher = new TextSearcher(sysTextArea); 158 159 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 160 setTitle(WINDOW_TITLE); 161 162 GroupLayout mcvTabLayout = new GroupLayout(mcvTab); 163 mcvTab.setLayout(mcvTabLayout); 164 mcvTabLayout.setHorizontalGroup( 165 mcvTabLayout.createParallelGroup(LEADING) 166 .addComponent(mcvPanel) 167 ); 168 mcvTabLayout.setVerticalGroup( 169 mcvTabLayout.createParallelGroup(LEADING) 170 .addComponent(mcvPanel) 171 ); 172 173 tabbedPanel.addTab(MCV_TAB_TITLE, mcvTab); 174 175 sysTextArea.setText(PLEASE_WAIT); 176 177 sysTextArea.setEditable(false); 178 sysTextArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); 179 sysTextArea.setCaretPosition(0); 180 sysTextArea.setLineWrap(false); 181 sysTextArea.addKeyListener(new KeyAdapter() { 182 @Override public void keyPressed(KeyEvent e) { 183 if (McIDASV.isMac() && e.isMetaDown() && e.getKeyCode() == KeyEvent.VK_F) { 184 textSearcher.getFindFld().requestFocusInWindow(); 185 } else if (!McIDASV.isMac() && GuiUtils.isControlKey(e, KeyEvent.VK_F)) { 186 textSearcher.getFindFld().requestFocusInWindow(); 187 } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { 188 JTextField field = textSearcher.getFindFld(); 189 boolean highlights = textSearcher.getTextWrapper().hasHighlights(); 190 if (!field.getText().isEmpty() && highlights) { 191 textSearcher.getTextWrapper().removeHighlights(); 192 field.setText(""); 193 } 194 } 195 } 196 }); 197 198 sysScrollPane.setViewportView(sysTextArea); 199 200 sysTab.setLayout(new BorderLayout()); 201 sysTab.add(sysScrollPane); 202 sysTab.add(textSearcher, BorderLayout.SOUTH); 203 204 tabbedPanel.addTab(SYS_TAB_TITLE, sysTab); 205 206 GroupLayout layout = new GroupLayout(getContentPane()); 207 getContentPane().setLayout(layout); 208 layout.setHorizontalGroup( 209 layout.createParallelGroup(LEADING) 210 .addComponent(tabbedPanel) 211 ); 212 layout.setVerticalGroup( 213 layout.createParallelGroup(LEADING) 214 .addComponent(tabbedPanel) 215 ); 216 217 tabbedPanel.addChangeListener(this); 218 219 pack(); 220 setSize(600, 600); 221 setLocationRelativeTo(mcv.getIdvUIManager().getFrame()); 222 } 223 224 /** 225 * Populate the regular "About McIDAS-V" tab. 226 * <p> 227 * Contains information like build date, a link to our website, etc. 228 * </p> 229 * 230 * @return Panel suitable for using inside a {@link JTabbedPane}. 231 */ 232 private JPanel buildAboutMcv() { 233 StateManager stateManager = (StateManager)mcv.getStateManager(); 234 235 JEditorPane editor = new JEditorPane(); 236 editor.setEditable(false); 237 editor.setContentType("text/html"); 238 String html = stateManager.getMcIdasVersionAbout(); 239 editor.setText(html); 240 editor.setBackground(new JPanel().getBackground()); 241 editor.addHyperlinkListener(mcv); 242 243 String splashIcon = mcv.getProperty(Constants.PROP_SPLASHICON, ""); 244 final JLabel iconLbl = new JLabel(getImageIcon(splashIcon)); 245 246 iconLbl.setToolTipText("McIDAS-V Homepage"); 247 iconLbl.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 248 iconLbl.addMouseListener(new MouseAdapter() { 249 public void mouseClicked(MouseEvent evt) { 250 String url = mcv.getProperty(Constants.PROP_HOMEPAGE, ""); 251 try { 252 HyperlinkEvent link = new HyperlinkEvent( 253 iconLbl, 254 HyperlinkEvent.EventType.ACTIVATED, 255 new URL(url) 256 ); 257 mcv.hyperlinkUpdate(link); 258 } catch (MalformedURLException e) { 259 logger.warn("Malformed URL: '"+url+"'", e); 260 } 261 } 262 }); 263 264 JPanel contents = topCenter(inset(iconLbl, 5), inset(editor, 5)); 265 contents.setBorder(createBevelBorder(RAISED, GRAY, GRAY)); 266 return contents; 267 } 268 269 /** 270 * Populates the {@literal "System Information"} tab. 271 * <p> 272 * The system information is collected on a separate thread, and when done, 273 * the results are added to the tab (on the Event Dispatch Thread). 274 */ 275 private void populateSystemTab() { 276 Misc.runInABit(500, () -> { 277 if (!hasSysInfo.get()) { 278 String sysInfo = getSystemInformation(); 279 SwingUtilities.invokeLater(() -> { 280 // the caret manipulation is done so that the "append" 281 // call doesn't result in the text area being 282 // auto-scrolled to the end. however, it's also nice to 283 // have the caret available so that keystroke navigation 284 // of the text area still works. 285 DefaultCaret caret = (DefaultCaret)sysTextArea.getCaret(); 286 caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); 287 sysTextArea.setText(sysInfo); 288 caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); 289 hasSysInfo.set(true); 290 sysTextArea.requestFocus(); 291 }); 292 } 293 }); 294 } 295 296 /** 297 * Respond to a change in the {@link JTabbedPane}. 298 * <p> 299 * If the user has decided to make the {@literal "System Information"} tab 300 * visible, this method will call {@link #populateSystemTab()}. 301 * 302 * @param e Event that represents the state change. Cannot be {@code null}. 303 */ 304 public void stateChanged(ChangeEvent e) { 305 JTabbedPane tabPane = (JTabbedPane)e.getSource(); 306 int newIndex = tabPane.getSelectedIndex(); 307 if (newIndex == 1) { 308 populateSystemTab(); 309 } 310 } 311}